Detach value newtype (#4498)

Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
Mees Delzenne 2024-08-21 14:34:16 +02:00 committed by GitHub
parent e25eca71d7
commit 4c3f4d0fd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 2982 additions and 1455 deletions

1
Cargo.lock generated
View file

@ -6053,6 +6053,7 @@ dependencies = [
"serial_test",
"surrealdb",
"surrealdb-async-graphql-axum",
"surrealdb-core",
"temp-env",
"tempfile",
"test-log",

View file

@ -27,8 +27,8 @@ ml = ["surrealdb/ml"]
jwks = ["surrealdb/jwks"]
performance-profiler = ["dep:pprof"]
# Special features
storage-fdb-7_1 = ["surrealdb/kv-fdb-7_1"]
storage-fdb-7_3 = ["surrealdb/kv-fdb-7_3"]
storage-fdb-7_1 = ["surrealdb-core/kv-fdb-7_1"]
storage-fdb-7_3 = ["surrealdb-core/kv-fdb-7_3"]
[workspace]
members = [
@ -123,6 +123,7 @@ surrealdb = { version = "2", path = "lib", features = [
"protocol-ws",
"rustls",
] }
surrealdb-core = { version = "2", path = "core" }
tempfile = "3.8.1"
thiserror = "1.0.50"
tokio = { version = "1.34.0", features = ["macros", "signal"] }

View file

@ -967,6 +967,7 @@ allow_unsafe = true
allow_apis = ["fs"]
[pkg.surrealdb]
allow_unsafe = true
allow_apis = ["fs"]
build.allow_build_instructions = ["cargo::rustc-check-cfg=*"]

View file

@ -1,5 +1,5 @@
use crate::err::Error;
use crate::sql::value::Value;
use crate::sql::Value as CoreValue;
use revision::revisioned;
use revision::Revisioned;
use serde::ser::SerializeStruct;
@ -25,7 +25,7 @@ pub enum QueryType {
#[non_exhaustive]
pub struct Response {
pub time: Duration,
pub result: Result<Value, Error>,
pub result: Result<CoreValue, Error>,
// Record the query type in case processing the response is necessary (such as tracking live queries).
pub query_type: QueryType,
}
@ -37,7 +37,7 @@ impl Response {
}
/// Retrieve the response as a normal result
pub fn output(self) -> Result<Value, Error> {
pub fn output(self) -> Result<CoreValue, Error> {
self.result
}
}
@ -66,7 +66,7 @@ impl Serialize for Response {
}
Err(e) => {
val.serialize_field("status", &Status::Err)?;
val.serialize_field("result", &Value::from(e.to_string()))?;
val.serialize_field("result", &CoreValue::from(e.to_string()))?;
}
}
val.end()
@ -80,7 +80,7 @@ impl Serialize for Response {
pub struct QueryMethodResponse {
pub time: String,
pub status: Status,
pub result: Value,
pub result: CoreValue,
}
impl From<&Response> for QueryMethodResponse {
@ -88,7 +88,7 @@ impl From<&Response> for QueryMethodResponse {
let time = res.speed();
let (status, result) = match &res.result {
Ok(value) => (Status::Ok, value.clone()),
Err(error) => (Status::Err, Value::from(error.to_string())),
Err(error) => (Status::Err, CoreValue::from(error.to_string())),
};
Self {
status,

View file

@ -10,6 +10,7 @@ use crate::vs::Versionstamp;
use std::fmt::Debug;
use std::ops::Range;
#[allow(dead_code)] // not used when non of the storage backends are enabled.
pub trait Transaction {
/// Specify how we should handle unclosed transactions.
///

View file

@ -21,9 +21,11 @@ pub enum SizedClock {
}
impl SizedClock {
#[allow(dead_code)] // not used when non of the storage backends are enabled.
pub(crate) fn system() -> Self {
Self::System(Default::default())
}
pub async fn now(&self) -> Timestamp {
match self {
SizedClock::System(c) => c.now(),

View file

@ -5,6 +5,7 @@ pub type Key = Vec<u8>;
pub type Val = Vec<u8>;
/// This trait appends an element to a collection, and allows chaining
#[allow(dead_code)] // not used when non of the storage backends are enabled.
pub(super) trait Add<T> {
fn add(self, v: T) -> Self;
}

View file

@ -1,3 +1,4 @@
#[allow(unused_imports)] // not used when non of the storage backends are enabled.
use super::api::Transaction;
use super::Key;
use super::Val;
@ -125,12 +126,26 @@ macro_rules! expand_inner {
#[cfg(feature = "kv-surrealkv")]
Inner::SurrealKV($arm) => $b,
#[allow(unreachable_patterns)]
_ => unreachable!(),
_ => {
unreachable!();
}
}
};
}
impl Transactor {
// Allow unused_variables when no storage is enabled as none of the values are used then.
#![cfg_attr(
not(any(
feature = "kv-mem",
feature = "kv-rocksdb",
feature = "kv-indxdb",
feature = "kv-tikv",
feature = "kv-fdb",
feature = "kv-surrealkv",
)),
allow(unused_variables)
)]
// --------------------------------------------------
// Integral methods
// --------------------------------------------------

View file

@ -47,7 +47,7 @@ impl RpcContext for BasicRpcContext<'_> {
&mut self.vars
}
fn version_data(&self) -> impl Into<super::Data> {
Value::Strand(self.version_string.clone().into())
fn version_data(&self) -> super::Data {
Value::Strand(self.version_string.clone().into()).into()
}
}

View file

@ -22,7 +22,7 @@ pub trait RpcContext {
fn session_mut(&mut self) -> &mut Session;
fn vars(&self) -> &BTreeMap<String, Value>;
fn vars_mut(&mut self) -> &mut BTreeMap<String, Value>;
fn version_data(&self) -> impl Into<Data>;
fn version_data(&self) -> Data;
const LQ_SUPPORT: bool = false;
fn handle_live(&self, _lqid: &Uuid) -> impl std::future::Future<Output = ()> + Send {
@ -98,7 +98,7 @@ pub trait RpcContext {
// Methods for authentication
// ------------------------------
async fn yuse(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn yuse(&mut self, params: Array) -> Result<Data, RpcError> {
// For both ns+db, string = change, null = unset, none = do nothing
// We need to be able to adjust either ns or db without affecting the other
// To be able to select a namespace, and then list resources in that namespace, as an example
@ -123,10 +123,10 @@ pub trait RpcContext {
self.session_mut().db = Some(db.0);
}
Ok(Value::None)
Ok(Value::None.into())
}
async fn signup(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn signup(&mut self, params: Array) -> Result<Data, RpcError> {
let Ok(Value::Object(v)) = params.needs_one() else {
return Err(RpcError::InvalidParams);
};
@ -139,11 +139,10 @@ pub trait RpcContext {
.map_err(Into::into);
*self.session_mut() = tmp_session;
out
out.map(Into::into)
}
async fn signin(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn signin(&mut self, params: Array) -> Result<Data, RpcError> {
let Ok(Value::Object(v)) = params.needs_one() else {
return Err(RpcError::InvalidParams);
};
@ -154,29 +153,29 @@ pub trait RpcContext {
.map(Into::into)
.map_err(Into::into);
*self.session_mut() = tmp_session;
out
out.map(Into::into)
}
async fn invalidate(&mut self) -> Result<impl Into<Data>, RpcError> {
async fn invalidate(&mut self) -> Result<Data, RpcError> {
crate::iam::clear::clear(self.session_mut())?;
Ok(Value::None)
Ok(Value::None.into())
}
async fn authenticate(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn authenticate(&mut self, params: Array) -> Result<Data, RpcError> {
let Ok(Value::Strand(token)) = params.needs_one() else {
return Err(RpcError::InvalidParams);
};
let mut tmp_session = mem::take(self.session_mut());
crate::iam::verify::token(self.kvs(), &mut tmp_session, &token.0).await?;
*self.session_mut() = tmp_session;
Ok(Value::None)
Ok(Value::None.into())
}
// ------------------------------
// Methods for identification
// ------------------------------
async fn info(&self) -> Result<impl Into<Data>, RpcError> {
async fn info(&self) -> Result<Data, RpcError> {
// Specify the SQL query string
let sql = "SELECT * FROM $auth";
// Execute the query on the database
@ -184,14 +183,14 @@ pub trait RpcContext {
// Extract the first value from the result
let res = res.remove(0).result?.first();
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for setting variables
// ------------------------------
async fn set(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn set(&mut self, params: Array) -> Result<Data, RpcError> {
let Ok((Value::Strand(key), val)) = params.needs_one_or_two() else {
return Err(RpcError::InvalidParams);
};
@ -207,22 +206,22 @@ pub trait RpcContext {
// Store the variable if defined
v => self.vars_mut().insert(key.0, v),
};
Ok(Value::Null)
Ok(Value::Null.into())
}
async fn unset(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn unset(&mut self, params: Array) -> Result<Data, RpcError> {
let Ok(Value::Strand(key)) = params.needs_one() else {
return Err(RpcError::InvalidParams);
};
self.vars_mut().remove(&key.0);
Ok(Value::Null)
Ok(Value::Null.into())
}
// ------------------------------
// Methods for live queries
// ------------------------------
async fn kill(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn kill(&mut self, params: Array) -> Result<Data, RpcError> {
let id = params.needs_one()?;
// Specify the SQL query string
let sql = "KILL $id";
@ -236,10 +235,10 @@ pub trait RpcContext {
let mut res = self.query_inner(Value::from(sql), Some(var)).await?;
// Extract the first query result
let response = res.remove(0);
response.result.map_err(Into::into)
response.result.map_err(Into::into).map(Into::into)
}
async fn live(&mut self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn live(&mut self, params: Array) -> Result<Data, RpcError> {
let (tb, diff) = params.needs_one_or_two()?;
// Specify the SQL query string
let sql = match diff.is_true() {
@ -255,14 +254,14 @@ pub trait RpcContext {
let mut res = self.query_inner(Value::from(sql), Some(var)).await?;
// Extract the first query result
let response = res.remove(0);
response.result.map_err(Into::into)
response.result.map_err(Into::into).map(Into::into)
}
// ------------------------------
// Methods for selecting
// ------------------------------
async fn select(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn select(&self, params: Array) -> Result<Data, RpcError> {
let Ok(what) = params.needs_one() else {
return Err(RpcError::InvalidParams);
};
@ -283,14 +282,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for inserting
// ------------------------------
async fn insert(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn insert(&self, params: Array) -> Result<Data, RpcError> {
let Ok((what, data)) = params.needs_two() else {
return Err(RpcError::InvalidParams);
};
@ -312,14 +311,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for creating
// ------------------------------
async fn create(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn create(&self, params: Array) -> Result<Data, RpcError> {
let Ok((what, data)) = params.needs_one_or_two() else {
return Err(RpcError::InvalidParams);
};
@ -345,14 +344,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for upserting
// ------------------------------
async fn upsert(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn upsert(&self, params: Array) -> Result<Data, RpcError> {
let Ok((what, data)) = params.needs_one_or_two() else {
return Err(RpcError::InvalidParams);
};
@ -378,14 +377,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for updating
// ------------------------------
async fn update(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn update(&self, params: Array) -> Result<Data, RpcError> {
let Ok((what, data)) = params.needs_one_or_two() else {
return Err(RpcError::InvalidParams);
};
@ -411,14 +410,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for merging
// ------------------------------
async fn merge(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn merge(&self, params: Array) -> Result<Data, RpcError> {
let Ok((what, data)) = params.needs_one_or_two() else {
return Err(RpcError::InvalidParams);
};
@ -444,14 +443,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for patching
// ------------------------------
async fn patch(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn patch(&self, params: Array) -> Result<Data, RpcError> {
let Ok((what, data, diff)) = params.needs_one_two_or_three() else {
return Err(RpcError::InvalidParams);
};
@ -476,14 +475,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for relating
// ------------------------------
async fn relate(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn relate(&self, params: Array) -> Result<Data, RpcError> {
let Ok((from, kind, to, data)) = params.needs_three_or_four() else {
return Err(RpcError::InvalidParams);
};
@ -511,14 +510,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for deleting
// ------------------------------
async fn delete(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn delete(&self, params: Array) -> Result<Data, RpcError> {
let Ok(what) = params.needs_one() else {
return Err(RpcError::InvalidParams);
};
@ -539,14 +538,14 @@ pub trait RpcContext {
false => res.remove(0).result?,
};
// Return the result to the client
Ok(res)
Ok(res.into())
}
// ------------------------------
// Methods for getting info
// ------------------------------
async fn version(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn version(&self, params: Array) -> Result<Data, RpcError> {
match params.len() {
0 => Ok(self.version_data()),
_ => Err(RpcError::InvalidParams),
@ -557,7 +556,7 @@ pub trait RpcContext {
// Methods for querying
// ------------------------------
async fn query(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn query(&self, params: Array) -> Result<Data, RpcError> {
let Ok((query, o)) = params.needs_one_or_two() else {
return Err(RpcError::InvalidParams);
};
@ -576,14 +575,14 @@ pub trait RpcContext {
Some(mut v) => Some(mrg! {v.0, &self.vars()}),
None => Some(self.vars().clone()),
};
self.query_inner(query, vars).await
self.query_inner(query, vars).await.map(Into::into)
}
// ------------------------------
// Methods for running functions
// ------------------------------
async fn run(&self, params: Array) -> Result<impl Into<Data>, RpcError> {
async fn run(&self, params: Array) -> Result<Data, RpcError> {
let Ok((Value::Strand(Strand(func_name)), version, args)) = params.needs_one_two_or_three()
else {
return Err(RpcError::InvalidParams);
@ -616,7 +615,7 @@ pub trait RpcContext {
.kvs()
.process(Statement::Value(func).into(), self.session(), Some(self.vars().clone()))
.await?;
res.remove(0).result.map_err(Into::into)
res.remove(0).result.map_err(Into::into).map(Into::into)
}
// ------------------------------

View file

@ -18,6 +18,17 @@ pub struct Edges {
pub what: Tables,
}
impl Edges {
#[doc(hidden)]
pub fn new(dir: Dir, from: Thing, what: Tables) -> Self {
Edges {
dir,
from,
what,
}
}
}
impl fmt::Display for Edges {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.what.len() {

View file

@ -14,7 +14,6 @@ use std::{cmp::Ordering, fmt, ops::Bound};
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub struct IdRange {
pub beg: Bound<Id>,
pub end: Bound<Id>,

View file

@ -9,7 +9,6 @@ use derive::Store;
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Deref;
use std::sync::Arc;
#[revisioned(revision = 3)]
@ -312,17 +311,18 @@ impl InfoStatement {
opt.is_allowed(Action::View, ResourceKind::Actor, &Base::Db)?;
// Get the transaction
let txn = ctx.tx();
// Obtain the index
let res = txn.get_tb_index(opt.ns()?, opt.db()?, table, index).await?;
// Output
let mut out = Object::default();
#[cfg(not(target_arch = "wasm32"))]
if let Some(ib) = ctx.get_index_builder() {
if let Some(status) = ib.get_status(res.deref()).await {
// Obtain the index
let res = txn.get_tb_index(opt.ns()?, opt.db()?, table, index).await?;
if let Some(status) = ib.get_status(&res).await {
let mut out = Object::default();
out.insert("building".to_string(), status.into());
return Ok(out.into());
}
}
Ok(out.into())
Ok(Object::default().into())
}
}
}

View file

@ -0,0 +1,205 @@
pub(super) mod vec;
use crate::err::Error;
use crate::sql::value::serde::ser;
use crate::sql::Ident;
use crate::sql::Part;
use crate::sql::Value;
use ser::Serializer as _;
use serde::ser::Error as _;
use serde::ser::Impossible;
use serde::ser::Serialize;
pub(super) struct Serializer;
impl ser::Serializer for Serializer {
type Ok = Part;
type Error = Error;
type SerializeSeq = Impossible<Part, Error>;
type SerializeTuple = Impossible<Part, Error>;
type SerializeTupleStruct = Impossible<Part, Error>;
type SerializeTupleVariant = SerializePart;
type SerializeMap = Impossible<Part, Error>;
type SerializeStruct = Impossible<Part, Error>;
type SerializeStructVariant = Impossible<Part, Error>;
const EXPECTED: &'static str = "an enum `Part`";
#[inline]
fn serialize_unit_variant(
self,
name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Error> {
match variant {
"All" => Ok(Part::All),
"Last" => Ok(Part::Last),
"First" => Ok(Part::First),
variant => Err(Error::custom(format!("unexpected unit variant `{name}::{variant}`"))),
}
}
#[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 {
"Field" => Ok(Part::Field(Ident(value.serialize(ser::string::Serializer.wrap())?))),
"Index" => Ok(Part::Index(value.serialize(ser::number::Serializer.wrap())?)),
"Where" => Ok(Part::Where(value.serialize(ser::value::Serializer.wrap())?)),
"Graph" => Ok(Part::Graph(value.serialize(ser::graph::Serializer.wrap())?)),
"Start" => Ok(Part::Start(value.serialize(ser::value::Serializer.wrap())?)),
"Value" => Ok(Part::Value(value.serialize(ser::value::Serializer.wrap())?)),
variant => {
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
}
}
}
fn serialize_tuple_variant(
self,
name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
let inner = match variant {
"Method" => Inner::Method(Default::default(), Default::default()),
variant => {
return Err(Error::custom(format!("unexpected tuple variant `{name}::{variant}`")));
}
};
Ok(SerializePart {
inner,
index: 0,
})
}
}
pub(super) struct SerializePart {
index: usize,
inner: Inner,
}
enum Inner {
Method(String, Vec<Value>),
}
impl serde::ser::SerializeTupleVariant for SerializePart {
type Ok = Part;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: Serialize + ?Sized,
{
match (self.index, &mut self.inner) {
(0, Inner::Method(ref mut var, _)) => {
*var = value.serialize(ser::string::Serializer.wrap())?;
}
(1, Inner::Method(_, ref mut var)) => {
*var = value.serialize(ser::value::vec::Serializer.wrap())?;
}
(index, inner) => {
let variant = match inner {
Inner::Method(..) => "Method",
};
return Err(Error::custom(format!("unexpected `Part::{variant}` index `{index}`")));
}
}
self.index += 1;
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
match self.inner {
Inner::Method(one, two) => Ok(Part::Method(one, two)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql;
use serde::Serialize;
#[test]
fn all() {
let part = Part::All;
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn last() {
let part = Part::Last;
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn first() {
let part = Part::First;
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn field() {
let part = Part::Field(Default::default());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn index() {
let part = Part::Index(Default::default());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn r#where() {
let part = Part::Where(Default::default());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn graph() {
let part = Part::Graph(Default::default());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn start() {
let part = Part::Start(sql::thing("foo:bar").unwrap().into());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn value() {
let part = Part::Value(sql::thing("foo:bar").unwrap().into());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
#[test]
fn method() {
let part = Part::Method(Default::default(), Default::default());
let serialized = part.serialize(Serializer.wrap()).unwrap();
assert_eq!(part, serialized);
}
}

View file

@ -2,7 +2,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughpu
use std::time::Duration;
use surrealdb::dbs::Session;
use surrealdb::kvs::Datastore;
use surrealdb::sql::Value;
use surrealdb::Value;
use tokio::runtime::Runtime;
fn bench_processor(c: &mut Criterion) {

View file

@ -27,7 +27,7 @@ impl super::Routine for Read {
tasks.spawn(async move {
let _: Option<Record> = client
.create((table_name, task_id as u64))
.create((table_name, task_id as i64))
.content(Record {
field: Id::rand(),
})
@ -53,7 +53,7 @@ impl super::Routine for Read {
tasks.spawn(async move {
let _: Option<Record> = criterion::black_box(
client
.select((table_name, task_id as u64))
.select((table_name, task_id as i64))
.await
.expect("[run] select operation failed")
.expect("[run] the select operation returned None"),

View file

@ -1,18 +1,17 @@
use super::MlExportConfig;
use crate::Result;
use crate::{opt::Resource, value::Notification, Result};
use bincode::Options;
use channel::Sender;
use revision::Revisioned;
use serde::{ser::SerializeMap as _, Serialize};
use std::io::Read;
use std::path::PathBuf;
use std::{collections::BTreeMap, io::Read};
use surrealdb_core::sql::Array;
use surrealdb_core::{
dbs::Notification,
sql::{Object, Query, Value},
};
use surrealdb_core::sql::{Array as CoreArray, Object as CoreObject, Query, Value as CoreValue};
use uuid::Uuid;
#[cfg(any(feature = "protocol-ws", feature = "protocol-http"))]
use surrealdb_core::sql::Table as CoreTable;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub(crate) enum Command {
@ -21,48 +20,49 @@ pub(crate) enum Command {
database: Option<String>,
},
Signup {
credentials: Object,
credentials: CoreObject,
},
Signin {
credentials: Object,
credentials: CoreObject,
},
Authenticate {
token: String,
},
Invalidate,
Create {
what: Value,
data: Option<Value>,
what: Resource,
data: Option<CoreValue>,
},
Upsert {
what: Value,
data: Option<Value>,
what: Resource,
data: Option<CoreValue>,
},
Update {
what: Value,
data: Option<Value>,
what: Resource,
data: Option<CoreValue>,
},
Insert {
what: Option<Value>,
data: Value,
// inserts can only be on a table.
what: String,
data: CoreValue,
},
Patch {
what: Value,
data: Option<Value>,
what: Resource,
data: Option<CoreValue>,
},
Merge {
what: Value,
data: Option<Value>,
what: Resource,
data: Option<CoreValue>,
},
Select {
what: Value,
what: Resource,
},
Delete {
what: Value,
what: Resource,
},
Query {
query: Query,
variables: BTreeMap<String, Value>,
variables: CoreObject,
},
ExportFile {
path: PathBuf,
@ -88,14 +88,14 @@ pub(crate) enum Command {
Version,
Set {
key: String,
value: Value,
value: CoreValue,
},
Unset {
key: String,
},
SubscribeLive {
uuid: Uuid,
notification_sender: Sender<Notification>,
notification_sender: Sender<Notification<CoreValue>>,
},
Kill {
uuid: Uuid,
@ -103,61 +103,60 @@ pub(crate) enum Command {
Run {
name: String,
version: Option<String>,
args: Array,
args: CoreArray,
},
}
impl Command {
#[cfg(any(feature = "protocol-ws", feature = "protocol-http"))]
pub(crate) fn into_router_request(self, id: Option<i64>) -> Option<RouterRequest> {
let id = id.map(Value::from);
let res = match self {
Command::Use {
namespace,
database,
} => RouterRequest {
id,
method: Value::from("use"),
params: Some(vec![Value::from(namespace), Value::from(database)].into()),
method: "use",
params: Some(vec![CoreValue::from(namespace), CoreValue::from(database)].into()),
},
Command::Signup {
credentials,
} => RouterRequest {
id,
method: "signup".into(),
params: Some(vec![Value::from(credentials)].into()),
method: "signup",
params: Some(vec![CoreValue::from(credentials)].into()),
},
Command::Signin {
credentials,
} => RouterRequest {
id,
method: "signin".into(),
params: Some(vec![Value::from(credentials)].into()),
method: "signin",
params: Some(vec![CoreValue::from(credentials)].into()),
},
Command::Authenticate {
token,
} => RouterRequest {
id,
method: "authenticate".into(),
params: Some(vec![Value::from(token)].into()),
method: "authenticate",
params: Some(vec![CoreValue::from(token)].into()),
},
Command::Invalidate => RouterRequest {
id,
method: "invalidate".into(),
method: "invalidate",
params: None,
},
Command::Create {
what,
data,
} => {
let mut params = vec![what];
let mut params = vec![what.into_core_value()];
if let Some(data) = data {
params.push(data);
}
RouterRequest {
id,
method: "create".into(),
method: "create",
params: Some(params.into()),
}
}
@ -166,14 +165,14 @@ impl Command {
data,
..
} => {
let mut params = vec![what];
let mut params = vec![what.into_core_value()];
if let Some(data) = data {
params.push(data);
}
RouterRequest {
id,
method: "upsert".into(),
method: "upsert",
params: Some(params.into()),
}
}
@ -182,7 +181,7 @@ impl Command {
data,
..
} => {
let mut params = vec![what];
let mut params = vec![what.into_core_value()];
if let Some(data) = data {
params.push(data);
@ -190,7 +189,7 @@ impl Command {
RouterRequest {
id,
method: "update".into(),
method: "update",
params: Some(params.into()),
}
}
@ -198,17 +197,13 @@ impl Command {
what,
data,
} => {
let mut params = if let Some(w) = what {
vec![w]
} else {
vec![Value::None]
};
params.push(data);
let mut table = CoreTable::default();
table.0 = what.clone();
let params = vec![CoreValue::from(what), data];
RouterRequest {
id,
method: "insert".into(),
method: "insert",
params: Some(params.into()),
}
}
@ -217,14 +212,15 @@ impl Command {
data,
..
} => {
let mut params = vec![what];
let mut params = vec![what.into_core_value()];
if let Some(data) = data {
params.push(data);
}
RouterRequest {
id,
method: "patch".into(),
method: "patch",
params: Some(params.into()),
}
}
@ -233,14 +229,14 @@ impl Command {
data,
..
} => {
let mut params = vec![what];
let mut params = vec![what.into_core_value()];
if let Some(data) = data {
params.push(data);
params.push(data)
}
RouterRequest {
id,
method: "merge".into(),
method: "merge",
params: Some(params.into()),
}
}
@ -249,25 +245,25 @@ impl Command {
..
} => RouterRequest {
id,
method: "select".into(),
params: Some(vec![what].into()),
method: "select",
params: Some(CoreValue::Array(vec![what.into_core_value()].into())),
},
Command::Delete {
what,
..
} => RouterRequest {
id,
method: "delete".into(),
params: Some(vec![what].into()),
method: "delete",
params: Some(CoreValue::Array(vec![what.into_core_value()].into())),
},
Command::Query {
query,
variables,
} => {
let params: Vec<Value> = vec![query.into(), variables.into()];
let params: Vec<CoreValue> = vec![query.into(), variables.into()];
RouterRequest {
id,
method: "query".into(),
method: "query",
params: Some(params.into()),
}
}
@ -291,12 +287,12 @@ impl Command {
} => return None,
Command::Health => RouterRequest {
id,
method: "ping".into(),
method: "ping",
params: None,
},
Command::Version => RouterRequest {
id,
method: "version".into(),
method: "version",
params: None,
},
Command::Set {
@ -304,15 +300,15 @@ impl Command {
value,
} => RouterRequest {
id,
method: "let".into(),
params: Some(vec![Value::from(key), value].into()),
method: "let",
params: Some(CoreValue::from(vec![CoreValue::from(key), value])),
},
Command::Unset {
key,
} => RouterRequest {
id,
method: "unset".into(),
params: Some(vec![Value::from(key)].into()),
method: "unset",
params: Some(CoreValue::from(vec![CoreValue::from(key)])),
},
Command::SubscribeLive {
..
@ -321,8 +317,8 @@ impl Command {
uuid,
} => RouterRequest {
id,
method: "kill".into(),
params: Some(vec![Value::from(uuid)].into()),
method: "kill",
params: Some(CoreValue::from(vec![CoreValue::from(uuid)])),
},
Command::Run {
name,
@ -330,9 +326,10 @@ impl Command {
args,
} => RouterRequest {
id,
method: "run".into(),
method: "run",
params: Some(
vec![Value::from(name), Value::from(version), Value::Array(args)].into(),
vec![CoreValue::from(name), CoreValue::from(version), CoreValue::Array(args)]
.into(),
),
},
};
@ -340,34 +337,34 @@ impl Command {
}
#[cfg(feature = "protocol-http")]
pub(crate) fn needs_one(&self) -> bool {
pub(crate) fn needs_flatten(&self) -> bool {
match self {
Command::Upsert {
what,
..
} => what.is_thing(),
Command::Update {
}
| Command::Update {
what,
..
} => what.is_thing(),
}
| Command::Patch {
what,
..
}
| Command::Merge {
what,
..
}
| Command::Select {
what,
}
| Command::Delete {
what,
} => matches!(what, Resource::RecordId(_)),
Command::Insert {
data,
..
} => !data.is_array(),
Command::Patch {
what,
..
} => what.is_thing(),
Command::Merge {
what,
..
} => what.is_thing(),
Command::Select {
what,
} => what.is_thing(),
Command::Delete {
what,
} => what.is_thing(),
_ => false,
}
}
@ -378,9 +375,9 @@ impl Command {
/// This struct serializes as if it is a surrealdb_core::sql::Value::Object.
#[derive(Debug)]
pub(crate) struct RouterRequest {
id: Option<Value>,
method: Value,
params: Option<Value>,
id: Option<i64>,
method: &'static str,
params: Option<CoreValue>,
}
impl Serialize for RouterRequest {
@ -389,6 +386,47 @@ impl Serialize for RouterRequest {
S: serde::Serializer,
{
struct InnerRequest<'a>(&'a RouterRequest);
struct InnerNumberVariant(i64);
struct InnerNumber(i64);
struct InnerMethod(&'static str);
struct InnerStrand(&'static str);
struct InnerObject<'a>(&'a RouterRequest);
impl Serialize for InnerNumberVariant {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_variant("Value", 3, "Number", &InnerNumber(self.0))
}
}
impl Serialize for InnerNumber {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_variant("Number", 0, "Int", &self.0)
}
}
impl Serialize for InnerMethod {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_variant("Value", 4, "Strand", &InnerStrand(self.0))
}
}
impl Serialize for InnerStrand {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_struct("$surrealdb::private::sql::Strand", self.0)
}
}
impl Serialize for InnerRequest<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
@ -398,9 +436,9 @@ impl Serialize for RouterRequest {
let size = 1 + self.0.id.is_some() as usize + self.0.params.is_some() as usize;
let mut map = serializer.serialize_map(Some(size))?;
if let Some(id) = self.0.id.as_ref() {
map.serialize_entry("id", id)?;
map.serialize_entry("id", &InnerNumberVariant(*id))?;
}
map.serialize_entry("method", &self.0.method)?;
map.serialize_entry("method", &InnerMethod(self.0.method))?;
if let Some(params) = self.0.params.as_ref() {
map.serialize_entry("params", params)?;
}
@ -408,7 +446,21 @@ impl Serialize for RouterRequest {
}
}
serializer.serialize_newtype_variant("Value", 9, "Object", &InnerRequest(self))
impl Serialize for InnerObject<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_struct("Object", &InnerRequest(self.0))
}
}
serializer.serialize_newtype_variant(
"$surrealdb::private::sql::Value",
9,
"Object",
&InnerObject(self),
)
}
}
@ -441,12 +493,38 @@ impl Revisioned for RouterRequest {
serializer
.serialize_into(&mut *w, "id")
.map_err(|err| revision::Error::Serialize(err.to_string()))?;
// the Value version
1u16.serialize_revisioned(w)?;
// the Value::Number variant
3u16.serialize_revisioned(w)?;
// the Number version
1u16.serialize_revisioned(w)?;
// the Number::Int variant
0u16.serialize_revisioned(w)?;
x.serialize_revisioned(w)?;
}
serializer
.serialize_into(&mut *w, "method")
.map_err(|err| revision::Error::Serialize(err.to_string()))?;
self.method.serialize_revisioned(w)?;
// the Value version
1u16.serialize_revisioned(w)?;
// the Value::Strand variant
4u16.serialize_revisioned(w)?;
// the Strand version
1u16.serialize_revisioned(w)?;
serializer
.serialize_into(&mut *w, self.method)
.map_err(|e| revision::Error::Serialize(format!("{:?}", e)))?;
if let Some(x) = self.params.as_ref() {
serializer
@ -471,7 +549,7 @@ mod test {
use std::io::Cursor;
use revision::Revisioned;
use surrealdb_core::sql::Value;
use surrealdb_core::sql::{Number, Value};
use super::RouterRequest;
@ -485,16 +563,27 @@ mod test {
let Value::Object(obj) = val else {
panic!("not an object");
};
assert_eq!(obj.get("id").cloned(), req.id);
assert_eq!(obj.get("method").unwrap().clone(), req.method);
assert_eq!(
obj.get("id").cloned().and_then(|x| if let Value::Number(Number::Int(x)) = x {
Some(x)
} else {
None
}),
req.id
);
let Some(Value::Strand(x)) = obj.get("method") else {
panic!("invalid method field: {}", obj)
};
assert_eq!(x.0, req.method);
assert_eq!(obj.get("params").cloned(), req.params);
}
#[test]
fn router_request_value_conversion() {
let request = RouterRequest {
id: Some(Value::from(1234i64)),
method: Value::from("request"),
id: Some(1234),
method: "request",
params: Some(vec![Value::from(1234i64), Value::from("request")].into()),
};

View file

@ -6,16 +6,16 @@ use crate::api::opt::Endpoint;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use crate::Value;
use channel::Receiver;
use channel::Sender;
use serde::de::DeserializeOwned;
use std::collections::HashSet;
use std::sync::atomic::AtomicI64;
use std::sync::atomic::Ordering;
use surrealdb_core::sql::{from_value, Value};
use surrealdb_core::sql::{from_value as from_core_value, Value as CoreValue};
mod cmd;
pub(crate) use cmd::Command;
#[cfg(feature = "protocol-http")]
pub(crate) use cmd::RouterRequest;
@ -70,7 +70,7 @@ impl Router {
pub(crate) fn recv(
&self,
receiver: Receiver<Result<DbResponse>>,
) -> BoxFuture<'_, Result<Value>> {
) -> BoxFuture<'_, Result<CoreValue>> {
Box::pin(async move {
let response = receiver.recv().await?;
match response? {
@ -102,7 +102,7 @@ impl Router {
Box::pin(async move {
let rx = self.send(command).await?;
let value = self.recv(rx).await?;
from_value(value).map_err(Into::into)
from_core_value(value).map_err(Into::into)
})
}
@ -114,8 +114,8 @@ impl Router {
Box::pin(async move {
let rx = self.send(command).await?;
match self.recv(rx).await? {
Value::None | Value::Null => Ok(None),
value => from_value(value).map_err(Into::into),
CoreValue::None | CoreValue::Null => Ok(None),
value => from_core_value(value).map_err(Into::into),
}
})
}
@ -128,11 +128,11 @@ impl Router {
Box::pin(async move {
let rx = self.send(command).await?;
let value = match self.recv(rx).await? {
Value::None | Value::Null => Value::Array(Default::default()),
Value::Array(array) => Value::Array(array),
CoreValue::None | CoreValue::Null => return Ok(Vec::new()),
CoreValue::Array(array) => CoreValue::Array(array),
value => vec![value].into(),
};
from_value(value).map_err(Into::into)
from_core_value(value).map_err(Into::into)
})
}
@ -141,10 +141,10 @@ impl Router {
Box::pin(async move {
let rx = self.send(command).await?;
match self.recv(rx).await? {
Value::None | Value::Null => Ok(()),
Value::Array(array) if array.is_empty() => Ok(()),
CoreValue::None | CoreValue::Null => Ok(()),
CoreValue::Array(array) if array.is_empty() => Ok(()),
value => Err(Error::FromValue {
value,
value: Value::from_inner(value),
error: "expected the database to return nothing".to_owned(),
}
.into()),
@ -156,7 +156,7 @@ impl Router {
pub(crate) fn execute_value(&self, command: Command) -> BoxFuture<'_, Result<Value>> {
Box::pin(async move {
let rx = self.send(command).await?;
self.recv(rx).await
Ok(Value::from_inner(self.recv(rx).await?))
})
}
@ -175,7 +175,7 @@ pub enum DbResponse {
/// The response sent for the `query` method
Query(Response),
/// The response sent for any method except `query`
Other(Value),
Other(CoreValue),
}
#[derive(Debug, Clone)]

View file

@ -299,11 +299,13 @@ pub fn connect(address: impl IntoEndpoint) -> Connect<Any, Surreal<Any>> {
#[cfg(all(test, feature = "kv-mem"))]
mod tests {
use surrealdb_core::sql::Object;
use super::*;
use crate::opt::auth::Root;
use crate::opt::capabilities::Capabilities;
use crate::sql;
use crate::sql::Value;
use crate::Value;
#[tokio::test]
async fn local_engine_without_auth() {
@ -328,7 +330,11 @@ mod tests {
let mut res = db.query("INFO FOR ROOT").await.unwrap();
let users: Value = res.take("users").unwrap();
assert_eq!(users, sql::value("{}").unwrap(), "there should be no users in the system");
assert_eq!(
users.into_inner(),
Object::default().into(),
"there should be no users in the system"
);
}
#[tokio::test]

View file

@ -20,19 +20,14 @@
//! useful is to only enable the in-memory engine (`kv-mem`) during development. Besides letting you not
//! worry about those dependencies on your dev machine, it allows you to keep compile times low
//! during development while allowing you to test your code fully.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod native;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm;
use crate::{
api::{
conn::{Command, DbResponse, RequestData},
Connect, Response as QueryResponse, Result, Surreal,
},
method::Stats,
opt::IntoEndpoint,
opt::{IntoEndpoint, Resource as ApiResource, Table},
value::Notification,
};
use channel::Sender;
use indexmap::IndexMap;
@ -44,16 +39,18 @@ use std::{
time::Duration,
};
use surrealdb_core::{
dbs::{Notification, Response, Session},
dbs::{Response, Session},
iam,
kvs::Datastore,
sql::{
statements::{
CreateStatement, DeleteStatement, InsertStatement, KillStatement, SelectStatement,
UpdateStatement, UpsertStatement,
},
Data, Field, Output, Query, Statement, Uuid, Value,
Data, Field, Output, Query, Statement, Value as CoreValue,
},
};
use uuid::Uuid;
#[cfg(not(target_arch = "wasm32"))]
use crate::api::err::Error;
@ -80,7 +77,12 @@ use surrealdb_core::{
sql::statements::{DefineModelStatement, DefineStatement},
};
use super::value_to_values;
use super::resource_to_values;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod native;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm;
const DEFAULT_TICK_INTERVAL: Duration = Duration::from_secs(10);
@ -374,14 +376,19 @@ impl Surreal<Db> {
}
fn process(responses: Vec<Response>) -> QueryResponse {
let mut map = IndexMap::with_capacity(responses.len());
let mut map = IndexMap::<usize, (Stats, Result<CoreValue>)>::with_capacity(responses.len());
for (index, response) in responses.into_iter().enumerate() {
let stats = Stats {
execution_time: Some(response.time),
};
match response.result {
Ok(value) => map.insert(index, (stats, Ok(value))),
Err(error) => map.insert(index, (stats, Err(error.into()))),
Ok(value) => {
// Deserializing from a core value should always work.
map.insert(index, (stats, Ok(value)));
}
Err(error) => {
map.insert(index, (stats, Err(error.into())));
}
};
}
QueryResponse {
@ -390,25 +397,25 @@ fn process(responses: Vec<Response>) -> QueryResponse {
}
}
async fn take(one: bool, responses: Vec<Response>) -> Result<Value> {
async fn take(one: bool, responses: Vec<Response>) -> Result<CoreValue> {
if let Some((_stats, result)) = process(responses).results.swap_remove(&0) {
let value = result?;
match one {
true => match value {
Value::Array(mut array) => {
if let [value] = &mut array.0[..] {
return Ok(mem::take(value));
CoreValue::Array(mut array) => {
if let [ref mut value] = array[..] {
return Ok(mem::replace(value, CoreValue::None));
}
}
Value::None | Value::Null => {}
CoreValue::None | CoreValue::Null => {}
value => return Ok(value),
},
false => return Ok(value),
}
}
match one {
true => Ok(Value::None),
false => Ok(Value::Array(Default::default())),
true => Ok(CoreValue::None),
false => Ok(CoreValue::Array(Default::default())),
}
}
@ -476,8 +483,8 @@ async fn kill_live_query(
kvs: &Datastore,
id: Uuid,
session: &Session,
vars: BTreeMap<String, Value>,
) -> Result<Value> {
vars: BTreeMap<String, CoreValue>,
) -> Result<CoreValue> {
let mut query = Query::default();
let mut kill = KillStatement::default();
kill.id = id.into();
@ -493,8 +500,8 @@ async fn router(
}: RequestData,
kvs: &Arc<Datastore>,
session: &mut Session,
vars: &mut BTreeMap<String, Value>,
live_queries: &mut HashMap<Uuid, Sender<Notification>>,
vars: &mut BTreeMap<String, CoreValue>,
live_queries: &mut HashMap<Uuid, Sender<Notification<CoreValue>>>,
) -> Result<DbResponse> {
match command {
Command::Use {
@ -507,29 +514,29 @@ async fn router(
if let Some(db) = database {
session.db = Some(db);
}
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
Command::Signup {
credentials,
} => {
let response = crate::iam::signup::signup(kvs, session, credentials).await?;
let response = iam::signup::signup(kvs, session, credentials).await?;
Ok(DbResponse::Other(response.into()))
}
Command::Signin {
credentials,
} => {
let response = crate::iam::signin::signin(kvs, session, credentials).await?;
let response = iam::signin::signin(kvs, session, credentials).await?;
Ok(DbResponse::Other(response.into()))
}
Command::Authenticate {
token,
} => {
crate::iam::verify::token(kvs, session, &token).await?;
Ok(DbResponse::Other(Value::None))
iam::verify::token(kvs, session, &token).await?;
Ok(DbResponse::Other(CoreValue::None))
}
Command::Invalidate => {
crate::iam::clear::clear(session)?;
Ok(DbResponse::Other(Value::None))
iam::clear::clear(session)?;
Ok(DbResponse::Other(CoreValue::None))
}
Command::Create {
what,
@ -538,7 +545,7 @@ async fn router(
let mut query = Query::default();
let statement = {
let mut stmt = CreateStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.data = data.map(Data::ContentExpression);
stmt.output = Some(Output::After);
stmt
@ -553,16 +560,17 @@ async fn router(
data,
} => {
let mut query = Query::default();
let one = what.is_thing_single();
let one = matches!(what, ApiResource::RecordId(_));
let statement = {
let mut stmt = UpsertStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.data = data.map(Data::ContentExpression);
stmt.output = Some(Output::After);
stmt
};
query.0 .0 = vec![Statement::Upsert(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -571,16 +579,17 @@ async fn router(
data,
} => {
let mut query = Query::default();
let one = what.is_thing_single();
let one = matches!(what, ApiResource::RecordId(_));
let statement = {
let mut stmt = UpdateStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.data = data.map(Data::ContentExpression);
stmt.output = Some(Output::After);
stmt
};
query.0 .0 = vec![Statement::Update(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -592,13 +601,14 @@ async fn router(
let one = !data.is_array();
let statement = {
let mut stmt = InsertStatement::default();
stmt.into = what;
stmt.into = Some(Table(what).into_core().into());
stmt.data = Data::SingleExpression(data);
stmt.output = Some(Output::After);
stmt
};
query.0 .0 = vec![Statement::Insert(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -607,16 +617,17 @@ async fn router(
data,
} => {
let mut query = Query::default();
let one = what.is_thing_single();
let one = matches!(what, ApiResource::RecordId(_));
let statement = {
let mut stmt = UpdateStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.data = data.map(Data::PatchExpression);
stmt.output = Some(Output::After);
stmt
};
query.0 .0 = vec![Statement::Update(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -625,16 +636,17 @@ async fn router(
data,
} => {
let mut query = Query::default();
let one = what.is_thing_single();
let one = matches!(what, ApiResource::RecordId(_));
let statement = {
let mut stmt = UpdateStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.data = data.map(Data::MergeExpression);
stmt.output = Some(Output::After);
stmt
};
query.0 .0 = vec![Statement::Update(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -642,15 +654,16 @@ async fn router(
what,
} => {
let mut query = Query::default();
let one = what.is_thing_single();
let one = matches!(what, ApiResource::RecordId(_));
let statement = {
let mut stmt = SelectStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.expr.0 = vec![Field::All];
stmt
};
query.0 .0 = vec![Statement::Select(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -658,15 +671,16 @@ async fn router(
what,
} => {
let mut query = Query::default();
let one = what.is_thing_single();
let one = matches!(what, ApiResource::RecordId(_));
let statement = {
let mut stmt = DeleteStatement::default();
stmt.what = value_to_values(what);
stmt.what = resource_to_values(what);
stmt.output = Some(Output::Before);
stmt
};
query.0 .0 = vec![Statement::Delete(statement)];
let response = kvs.process(query, &*session, Some(vars.clone())).await?;
let vars = vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let response = kvs.process(query, &*session, Some(vars)).await?;
let value = take(one, response).await?;
Ok(DbResponse::Other(value))
}
@ -675,7 +689,7 @@ async fn router(
mut variables,
} => {
let mut vars = vars.clone();
vars.append(&mut variables);
vars.append(&mut variables.0);
let response = kvs.process(query, &*session, Some(vars)).await?;
let response = process(response);
Ok(DbResponse::Query(response))
@ -746,7 +760,7 @@ async fn router(
let copy = copy(file, &mut reader, &mut output);
tokio::try_join!(export, bridge, copy)?;
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(all(not(target_arch = "wasm32"), feature = "ml"))]
@ -793,7 +807,7 @@ async fn router(
let copy = copy(path, &mut reader, &mut output);
tokio::try_join!(export, bridge, copy)?;
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(not(target_arch = "wasm32"))]
@ -822,7 +836,7 @@ async fn router(
tokio::join!(export, bridge);
});
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(all(not(target_arch = "wasm32"), feature = "ml"))]
Command::ExportBytesMl {
@ -851,7 +865,7 @@ async fn router(
tokio::join!(export, bridge);
});
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(not(target_arch = "wasm32"))]
Command::ImportFile {
@ -882,7 +896,7 @@ async fn router(
response.result?;
}
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(all(not(target_arch = "wasm32"), feature = "ml"))]
Command::ImportMl {
@ -946,42 +960,46 @@ async fn router(
response.result?;
}
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
Command::Health => Ok(DbResponse::Other(CoreValue::None)),
Command::Version => {
Ok(DbResponse::Other(CoreValue::from(surrealdb_core::env::VERSION.to_string())))
}
Command::Health => Ok(DbResponse::Other(Value::None)),
Command::Version => Ok(DbResponse::Other(crate::env::VERSION.into())),
Command::Set {
key,
value,
} => {
let var = Some(map! {
key.clone() => Value::None,
=> vars
});
match kvs.compute(value, &*session, var).await? {
Value::None => vars.remove(&key),
let mut tmp_vars = vars.clone();
tmp_vars.insert(key.clone(), value.clone());
// Need to compute because certain keys might not be allowed to be set and those should
// be rejected by an error.
match kvs.compute(value, &*session, Some(tmp_vars)).await? {
CoreValue::None => vars.remove(&key),
v => vars.insert(key, v),
};
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
Command::Unset {
key,
} => {
vars.remove(&key);
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
Command::SubscribeLive {
uuid,
notification_sender,
} => {
live_queries.insert(uuid.into(), notification_sender);
Ok(DbResponse::Other(Value::None))
live_queries.insert(uuid, notification_sender);
Ok(DbResponse::Other(CoreValue::None))
}
Command::Kill {
uuid,
} => {
live_queries.remove(&uuid.into());
let value = kill_live_query(kvs, uuid.into(), session, vars.clone()).await?;
live_queries.remove(&uuid);
let value = kill_live_query(kvs, uuid, session, vars.clone()).await?;
Ok(DbResponse::Other(value))
}
@ -990,22 +1008,30 @@ async fn router(
version: _version,
args,
} => {
let func: Value = match &name[0..4] {
"fn::" => Function::Custom(name.chars().skip(4).collect(), args.0).into(),
// should return error, but can't on wasm
let func: CoreValue = if let Some(name) = name.strip_prefix("fn::") {
Function::Custom(name.to_owned(), args.0).into()
} else if let Some(_name) = name.strip_prefix("ml::") {
#[cfg(feature = "ml")]
"ml::" => {
let mut tmp = Model::default();
{
let mut model = Model::default();
tmp.name = name.chars().skip(4).collect();
tmp.args = args.0;
tmp.version = _version
model.name = _name.to_owned();
model.args = args.0;
model.version = _version
.ok_or(Error::Query("ML functions must have a version".to_string()))?;
tmp
model.into()
}
.into(),
_ => Function::Normal(name, args.0).into(),
#[cfg(not(feature = "ml"))]
{
return Err(crate::error::Db::InvalidModel {
message: "Machine learning computation is not enabled.".to_owned(),
}
.into());
}
} else {
Function::Custom(name, args.0).into()
};
let stmt = Statement::Value(func);
let response = kvs.process(stmt.into(), &*session, Some(vars.clone())).await?;

View file

@ -1,31 +1,24 @@
use crate::api::conn::Connection;
use crate::api::conn::Route;
use crate::api::conn::Router;
use crate::api::engine::local::Db;
use crate::api::method::BoxFuture;
use crate::api::opt::{Endpoint, EndpointKind};
use crate::api::ExtraFeatures;
use crate::api::OnceLockExt;
use crate::api::Result;
use crate::api::Surreal;
use crate::dbs::Session;
use crate::engine::tasks::start_tasks;
use crate::iam::Level;
use crate::kvs::Datastore;
use crate::opt::auth::Root;
use crate::opt::WaitFor;
use crate::options::EngineOptions;
use channel::Receiver;
use channel::Sender;
use futures::stream::poll_fn;
use futures::StreamExt;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::atomic::AtomicI64;
use std::sync::Arc;
use std::sync::OnceLock;
use std::task::Poll;
use crate::{
api::{
conn::{Connection, Route, Router},
engine::local::Db,
method::BoxFuture,
opt::{Endpoint, EndpointKind},
ExtraFeatures, OnceLockExt, Result, Surreal,
},
engine::tasks::start_tasks,
opt::{auth::Root, WaitFor},
value::Notification,
Action,
};
use channel::{Receiver, Sender};
use futures::{stream::poll_fn, StreamExt};
use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::{atomic::AtomicI64, Arc, OnceLock},
task::Poll,
};
use surrealdb_core::{dbs::Session, iam::Level, kvs::Datastore, options::EngineOptions};
use tokio::sync::watch;
impl crate::api::Connection for Db {}
@ -121,7 +114,7 @@ pub(crate) async fn run_router(
let kvs = kvs.with_temporary_directory(address.config.temporary_directory);
let kvs = Arc::new(kvs);
let mut vars = BTreeMap::new();
let mut vars = BTreeMap::default();
let mut live_queries = HashMap::new();
let mut session = Session::default().with_rt(true);
@ -166,8 +159,16 @@ pub(crate) async fn run_router(
// channel?
continue
};
let id = notification.id;
let notification = Notification{
query_id: *notification.id,
action: Action::from_core(notification.action),
data: notification.result
};
let id = notification.query_id;
if let Some(sender) = live_queries.get(&id) {
if sender.send(notification).await.is_err() {
live_queries.remove(&id);
if let Err(error) =

View file

@ -16,6 +16,7 @@ use crate::kvs::Datastore;
use crate::opt::auth::Root;
use crate::opt::WaitFor;
use crate::options::EngineOptions;
use crate::{Action, Notification};
use channel::{Receiver, Sender};
use futures::stream::poll_fn;
use futures::FutureExt;
@ -157,10 +158,17 @@ pub(crate) async fn run_router(
let id = notification.id;
if let Some(sender) = live_queries.get(&id) {
let notification = Notification {
query_id: notification.id.0,
action: Action::from_core(notification.action),
data: notification.result,
};
if sender.send(notification).await.is_err() {
live_queries.remove(&id);
if let Err(error) =
super::kill_live_query(&kvs, id, &session, vars.clone()).await
super::kill_live_query(&kvs, *id, &session, vars.clone()).await
{
warn!("Failed to kill live query '{id}'; {error}");
}

View file

@ -10,17 +10,17 @@ pub mod any;
feature = "kv-surrealkv",
))]
pub mod local;
pub mod proto;
#[cfg(any(feature = "protocol-http", feature = "protocol-ws"))]
pub mod remote;
#[doc(hidden)]
pub mod tasks;
use crate::sql::Value;
use crate::sql::Values;
use futures::Stream;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
use surrealdb_core::sql::Values as CoreValues;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
@ -30,21 +30,26 @@ use wasmtimer::std::Instant;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::Interval;
use crate::Value;
use super::opt::Resource;
use super::opt::Table;
// used in http and all local engines.
#[allow(dead_code)]
fn value_to_values(v: Value) -> Values {
match v {
Value::Array(x) => {
let mut values = Values::default();
values.0 = x.0;
values
}
x => {
let mut values = Values::default();
values.0 = vec![x];
values
fn resource_to_values(r: Resource) -> CoreValues {
let mut res = CoreValues::default();
match r {
Resource::Table(x) => {
res.0 = vec![Table(x).into_core().into()];
}
Resource::RecordId(x) => res.0 = vec![x.into_inner().into()],
Resource::Object(x) => res.0 = vec![x.into_inner().into()],
Resource::Array(x) => res.0 = Value::array_to_core(x),
Resource::Edge(x) => res.0 = vec![x.into_inner().into()],
Resource::Range(x) => res.0 = vec![x.into_inner().into()],
}
res
}
struct IntervalStream {

View file

@ -0,0 +1,41 @@
use revision::revisioned;
use serde::Deserialize;
use crate::Value;
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct Failure {
pub(crate) code: i64,
pub(crate) message: String,
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ResponseAction {
Create,
Update,
Delete,
}
#[revisioned(revision = 1)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[doc(hidden)]
#[non_exhaustive]
pub enum Status {
Ok,
Err,
}
#[revisioned(revision = 1)]
#[derive(Debug, Deserialize)]
#[doc(hidden)]
#[non_exhaustive]
pub struct QueryMethodResponse {
pub time: String,
pub status: Status,
pub result: Value,
}

View file

@ -1,10 +1,4 @@
//! HTTP engine
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod native;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm;
use crate::api::conn::Command;
use crate::api::conn::DbResponse;
use crate::api::conn::RequestData;
@ -20,8 +14,7 @@ use crate::headers::AUTH_NS;
use crate::headers::DB;
use crate::headers::NS;
use crate::opt::IntoEndpoint;
use crate::sql::from_value;
use crate::sql::Value;
use crate::Value;
use futures::TryStreamExt;
use indexmap::IndexMap;
use reqwest::header::HeaderMap;
@ -32,7 +25,10 @@ use reqwest::RequestBuilder;
use serde::Deserialize;
use serde::Serialize;
use std::marker::PhantomData;
use surrealdb_core::sql::Query;
use surrealdb_core::sql::{
from_value as from_core_value, statements::OutputStatement, Object as CoreObject, Param, Query,
Statement, Value as CoreValue,
};
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
@ -46,6 +42,11 @@ use tokio_util::compat::FuturesAsyncReadCompatExt;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod native;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm;
// const SQL_PATH: &str = "sql";
const RPC_PATH: &str = "rpc";
@ -165,7 +166,7 @@ struct AuthResponse {
type BackupSender = channel::Sender<Result<Vec<u8>>>;
#[cfg(not(target_arch = "wasm32"))]
async fn export_file(request: RequestBuilder, path: PathBuf) -> Result<Value> {
async fn export_file(request: RequestBuilder, path: PathBuf) -> Result<()> {
let mut response = request
.send()
.await?
@ -193,10 +194,10 @@ async fn export_file(request: RequestBuilder, path: PathBuf) -> Result<Value> {
.into());
}
Ok(Value::None)
Ok(())
}
async fn export_bytes(request: RequestBuilder, bytes: BackupSender) -> Result<Value> {
async fn export_bytes(request: RequestBuilder, bytes: BackupSender) -> Result<()> {
let response = request.send().await?.error_for_status()?;
let future = async move {
@ -214,11 +215,11 @@ async fn export_bytes(request: RequestBuilder, bytes: BackupSender) -> Result<Va
#[cfg(target_arch = "wasm32")]
spawn_local(future);
Ok(Value::None)
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
async fn import(request: RequestBuilder, path: PathBuf) -> Result<Value> {
async fn import(request: RequestBuilder, path: PathBuf) -> Result<()> {
let file = match OpenOptions::new().read(true).open(&path).await {
Ok(path) => path,
Err(error) => {
@ -248,15 +249,15 @@ async fn import(request: RequestBuilder, path: PathBuf) -> Result<Value> {
}
}
}
Ok(Value::None)
Ok(())
}
pub(crate) async fn health(request: RequestBuilder) -> Result<Value> {
pub(crate) async fn health(request: RequestBuilder) -> Result<()> {
request.send().await?.error_for_status()?;
Ok(Value::None)
Ok(())
}
async fn process_req(
async fn send_request(
req: RouterRequest,
base_url: &Url,
client: &reqwest::Client,
@ -269,34 +270,29 @@ async fn process_req(
let response = http_req.send().await?.error_for_status()?;
let bytes = response.bytes().await?;
let response: Response = deserialize(&mut &bytes[..], false)?;
DbResponse::from(response.result)
let response: Response = deserialize(&bytes, false)?;
DbResponse::from_server_result(response.result)
}
fn try_one(res: DbResponse, needed: bool) -> DbResponse {
if !needed {
return res;
}
fn flatten_dbresponse_array(res: DbResponse) -> DbResponse {
match res {
DbResponse::Other(Value::Array(arr)) if arr.len() == 1 => {
DbResponse::Other(arr.into_iter().next().unwrap())
DbResponse::Other(CoreValue::Array(array)) if array.len() == 1 => {
let v = array.into_iter().next().unwrap();
DbResponse::Other(v)
}
r => r,
x => x,
}
}
async fn router(
RequestData {
command,
..
}: RequestData,
req: RequestData,
base_url: &Url,
client: &reqwest::Client,
headers: &mut HeaderMap,
vars: &mut IndexMap<String, Value>,
vars: &mut IndexMap<String, CoreValue>,
auth: &mut Option<Auth>,
) -> Result<DbResponse> {
match command {
match req.command {
Command::Query {
query,
mut variables,
@ -308,40 +304,29 @@ async fn router(
}
.into_router_request(None)
.expect("query should be valid request");
process_req(req, base_url, client, headers, auth).await
send_request(req, base_url, client, headers, auth).await
}
ref cmd @ Command::Use {
ref namespace,
ref database,
Command::Use {
namespace,
database,
} => {
let req = cmd
.clone()
.into_router_request(None)
.expect("use should be a valid router request");
let req = Command::Use {
namespace: namespace.clone(),
database: database.clone(),
}
.into_router_request(None)
.unwrap();
// process request to check permissions
let out = process_req(req, base_url, client, headers, auth).await?;
match namespace {
Some(ns) => match HeaderValue::try_from(ns) {
Ok(ns) => {
headers.insert(&NS, ns);
}
Err(_) => {
return Err(Error::InvalidNsName(ns.to_owned()).into());
}
},
None => {}
let out = send_request(req, base_url, client, headers, auth).await?;
if let Some(ns) = namespace {
let value =
HeaderValue::try_from(&ns).map_err(|_| Error::InvalidNsName(ns.to_owned()))?;
headers.insert(&NS, value);
};
match database {
Some(db) => match HeaderValue::try_from(db) {
Ok(db) => {
headers.insert(&DB, db);
}
Err(_) => {
return Err(Error::InvalidDbName(db.to_owned()).into());
}
},
None => {}
if let Some(db) = database {
let value =
HeaderValue::try_from(&db).map_err(|_| Error::InvalidDbName(db.to_owned()))?;
headers.insert(&DB, value);
};
Ok(out)
@ -356,9 +341,12 @@ async fn router(
.expect("signin should be a valid router request");
let DbResponse::Other(value) =
process_req(req, base_url, client, headers, auth).await?
send_request(req, base_url, client, headers, auth).await?
else {
unreachable!("didn't make query")
return Err(Error::InternalError(
"recieved invalid result from server".to_string(),
)
.into());
};
if let Ok(Credentials {
@ -366,7 +354,7 @@ async fn router(
pass,
ns,
db,
}) = from_value(credentials.into())
}) = from_core_value(credentials.into())
{
*auth = Some(Auth::Basic {
user,
@ -390,44 +378,50 @@ async fn router(
}
.into_router_request(None)
.expect("authenticate should be a valid router request");
process_req(req, base_url, client, headers, auth).await?;
send_request(req, base_url, client, headers, auth).await?;
*auth = Some(Auth::Bearer {
token,
});
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
Command::Invalidate => {
*auth = None;
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
Command::Set {
key,
value,
} => {
let query: Query = surrealdb_core::sql::parse(&format!("RETURN ${key};"))?;
let mut output_stmt = OutputStatement::default();
output_stmt.what = CoreValue::Param(Param::from(key.clone()));
let query = Query::from(Statement::Output(output_stmt));
let mut variables = CoreObject::default();
variables.insert(key.clone(), value);
let req = Command::Query {
query,
variables: [(key.clone(), value)].into(),
variables,
}
.into_router_request(None)
.expect("query is valid request");
let DbResponse::Query(mut res) =
process_req(req, base_url, client, headers, auth).await?
send_request(req, base_url, client, headers, auth).await?
else {
unreachable!("made query request so response must be query")
return Err(Error::InternalError(
"recieved invalid result from server".to_string(),
)
.into());
};
let val: Value = res.take(0)?;
vars.insert(key, val);
Ok(DbResponse::Other(Value::None))
vars.insert(key, val.0);
Ok(DbResponse::Other(CoreValue::None))
}
Command::Unset {
key,
} => {
vars.shift_remove(&key);
Ok(DbResponse::Other(Value::None))
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(target_arch = "wasm32")]
Command::ExportFile {
@ -456,8 +450,8 @@ async fn router(
.headers(headers.clone())
.auth(auth)
.header(ACCEPT, "application/octet-stream");
let value = export_file(request, path).await?;
Ok(DbResponse::Other(value))
export_file(request, path).await?;
Ok(DbResponse::Other(CoreValue::None))
}
Command::ExportBytes {
bytes,
@ -468,8 +462,8 @@ async fn router(
.headers(headers.clone())
.auth(auth)
.header(ACCEPT, "application/octet-stream");
let value = export_bytes(request, bytes).await?;
Ok(DbResponse::Other(value))
export_bytes(request, bytes).await?;
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(not(target_arch = "wasm32"))]
Command::ExportMl {
@ -483,8 +477,8 @@ async fn router(
.headers(headers.clone())
.auth(auth)
.header(ACCEPT, "application/octet-stream");
let value = export_file(request, path).await?;
Ok(DbResponse::Other(value))
export_file(request, path).await?;
Ok(DbResponse::Other(CoreValue::None))
}
Command::ExportBytesMl {
bytes,
@ -497,8 +491,8 @@ async fn router(
.headers(headers.clone())
.auth(auth)
.header(ACCEPT, "application/octet-stream");
let value = export_bytes(request, bytes).await?;
Ok(DbResponse::Other(value))
export_bytes(request, bytes).await?;
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(not(target_arch = "wasm32"))]
Command::ImportFile {
@ -510,8 +504,8 @@ async fn router(
.headers(headers.clone())
.auth(auth)
.header(CONTENT_TYPE, "application/octet-stream");
let value = import(request, path).await?;
Ok(DbResponse::Other(value))
import(request, path).await?;
Ok(DbResponse::Other(CoreValue::None))
}
#[cfg(not(target_arch = "wasm32"))]
Command::ImportMl {
@ -523,19 +517,20 @@ async fn router(
.headers(headers.clone())
.auth(auth)
.header(CONTENT_TYPE, "application/octet-stream");
let value = import(request, path).await?;
Ok(DbResponse::Other(value))
import(request, path).await?;
Ok(DbResponse::Other(CoreValue::None))
}
Command::SubscribeLive {
..
} => Err(Error::LiveQueriesNotSupported.into()),
cmd => {
let one = cmd.needs_one();
let req = cmd
.into_router_request(None)
.expect("all invalid variants should have been caught");
process_req(req, base_url, client, headers, auth).await.map(|r| try_one(r, one))
let needs_flatten = cmd.needs_flatten();
let req = cmd.into_router_request(None).unwrap();
let mut res = send_request(req, base_url, client, headers, auth).await?;
if needs_flatten {
res = flatten_dbresponse_array(res);
}
Ok(res)
}
}
}

View file

@ -8,14 +8,8 @@ pub mod http;
#[cfg_attr(docsrs, doc(cfg(feature = "protocol-ws")))]
pub mod ws;
use crate::api;
use crate::api::conn::DbResponse;
use crate::api::err::Error;
use crate::api::method::query::QueryResult;
use crate::api::Result;
use crate::dbs::Notification;
use crate::dbs::QueryMethodResponse;
use crate::dbs::Status;
use crate::api::{self, conn::DbResponse, err::Error, method::query::QueryResult, Result};
use crate::dbs::{self, Status};
use crate::method::Stats;
use indexmap::IndexMap;
use revision::revisioned;
@ -24,14 +18,15 @@ use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use std::io::Read;
use std::time::Duration;
use surrealdb_core::sql::Value;
use surrealdb_core::sql::Value as CoreValue;
const NANOS_PER_SEC: i64 = 1_000_000_000;
const NANOS_PER_MILLI: i64 = 1_000_000;
const NANOS_PER_MICRO: i64 = 1_000;
pub struct WsNotification {}
// Converts a debug representation of `std::time::Duration` back
fn duration_from_str(duration: &str) -> Option<std::time::Duration> {
let nanos = if let Some(duration) = duration.strip_suffix("ns") {
@ -93,9 +88,9 @@ pub(crate) struct Failure {
#[revisioned(revision = 1)]
#[derive(Debug, Deserialize)]
pub(crate) enum Data {
Other(Value),
Query(Vec<QueryMethodResponse>),
Live(Notification),
Other(CoreValue),
Query(Vec<dbs::QueryMethodResponse>),
Live(dbs::Notification),
}
type ServerResult = std::result::Result<Data, Failure>;
@ -120,7 +115,7 @@ impl From<Failure> for crate::Error {
}
impl DbResponse {
fn from(result: ServerResult) -> Result<Self> {
fn from_server_result(result: ServerResult) -> Result<Self> {
match result.map_err(Error::from)? {
Data::Other(value) => Ok(DbResponse::Other(value)),
Data::Query(responses) => {
@ -159,7 +154,7 @@ impl DbResponse {
#[revisioned(revision = 1)]
#[derive(Debug, Deserialize)]
pub(crate) struct Response {
id: Option<Value>,
id: Option<CoreValue>,
pub(crate) result: ServerResult,
}
@ -172,18 +167,16 @@ where
value.serialize_revisioned(&mut buf).map_err(|error| crate::Error::Db(error.into()))?;
return Ok(buf);
}
crate::sql::serde::serialize(value).map_err(|error| crate::Error::Db(error.into()))
surrealdb_core::sql::serde::serialize(value).map_err(|error| crate::Error::Db(error.into()))
}
fn deserialize<A, T>(bytes: &mut A, revisioned: bool) -> Result<T>
fn deserialize<T>(bytes: &[u8], revisioned: bool) -> Result<T>
where
A: Read,
T: Revisioned + DeserializeOwned,
{
if revisioned {
return T::deserialize_revisioned(bytes).map_err(|x| crate::Error::Db(x.into()));
let mut read = std::io::Cursor::new(bytes);
return T::deserialize_revisioned(&mut read).map_err(|x| crate::Error::Db(x.into()));
}
let mut buf = Vec::new();
bytes.read_to_end(&mut buf).map_err(crate::err::Error::Io)?;
crate::sql::serde::deserialize(&buf).map_err(|error| crate::Error::Db(error.into()))
surrealdb_core::sql::serde::deserialize(bytes).map_err(|error| crate::Error::Db(error.into()))
}

View file

@ -11,13 +11,13 @@ use crate::api::Connect;
use crate::api::Result;
use crate::api::Surreal;
use crate::opt::IntoEndpoint;
use crate::sql::Value;
use crate::value::Notification;
use channel::Sender;
use indexmap::IndexMap;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::time::Duration;
use surrealdb_core::dbs::Notification as CoreNotification;
use surrealdb_core::sql::Value as CoreValue;
use trice::Instant;
use uuid::Uuid;
@ -29,7 +29,7 @@ enum RequestEffect {
/// Completing this request sets a variable to a give value.
Set {
key: String,
value: Value,
value: CoreValue,
},
/// Completing this request sets a variable to a give value.
Clear {
@ -59,11 +59,11 @@ struct PendingRequest {
struct RouterState<Sink, Stream> {
/// Vars currently set by the set method,
vars: IndexMap<String, Value>,
vars: IndexMap<String, CoreValue>,
/// Messages which aught to be replayed on a reconnect.
replay: IndexMap<ReplayMethod, Command>,
/// Pending live queries
live_queries: HashMap<Uuid, channel::Sender<CoreNotification>>,
live_queries: HashMap<Uuid, channel::Sender<Notification<CoreValue>>>,
/// Send requests which are still awaiting an awnser.
pending_requests: HashMap<i64, PendingRequest>,
/// The last time a message was recieved from the server.

View file

@ -19,7 +19,7 @@ use crate::api::Surreal;
use crate::engine::remote::Data;
use crate::engine::IntervalStream;
use crate::opt::WaitFor;
use crate::sql::Value;
use crate::{Action, Notification};
use channel::Receiver;
use futures::stream::{SplitSink, SplitStream};
use futures::SinkExt;
@ -31,6 +31,7 @@ use std::collections::HashSet;
use std::sync::atomic::AtomicI64;
use std::sync::Arc;
use std::sync::OnceLock;
use surrealdb_core::sql::Value as CoreValue;
use tokio::net::TcpStream;
use tokio::sync::watch;
use tokio::time;
@ -193,7 +194,7 @@ async fn router_handle_route(
ref notification_sender,
} => {
state.live_queries.insert(*uuid, notification_sender.clone());
if response.clone().send(Ok(DbResponse::Other(Value::None))).await.is_err() {
if response.clone().send(Ok(DbResponse::Other(CoreValue::None))).await.is_err() {
trace!("Receiver dropped");
}
// There is nothing to send to the server here
@ -278,27 +279,32 @@ async fn router_handle_response(
// If `id` is set this is a normal response
Some(id) => {
if let Ok(id) = id.coerce_to_i64() {
// We can only route responses with IDs
if let Some(pending) = state.pending_requests.remove(&id) {
let resp = match DbResponse::from_server_result(response.result) {
Ok(x) => x,
Err(e) => {
let _ = pending.response_channel.send(Err(e)).await;
return HandleResult::Ok;
}
};
// We can only route responses with IDs
match pending.effect {
RequestEffect::None => {}
RequestEffect::Insert => {
// For insert, we need to flatten single responses in an array
if let Ok(Data::Other(Value::Array(value))) =
response.result
{
if value.len() == 1 {
if let DbResponse::Other(CoreValue::Array(array)) = resp {
if array.len() == 1 {
let _ = pending
.response_channel
.send(DbResponse::from(Ok(Data::Other(
value.into_iter().next().unwrap(),
))))
.send(Ok(DbResponse::Other(
array.into_iter().next().unwrap(),
)))
.await;
} else {
let _ = pending
.response_channel
.send(DbResponse::from(Ok(Data::Other(
Value::Array(value),
.send(Ok(DbResponse::Other(CoreValue::Array(
array,
))))
.await;
}
@ -317,10 +323,7 @@ async fn router_handle_response(
state.vars.shift_remove(&key);
}
}
let _res = pending
.response_channel
.send(DbResponse::from(response.result))
.await;
let _res = pending.response_channel.send(Ok(resp)).await;
} else {
warn!("got response for request with id '{id}', which was not in pending requests")
}
@ -334,11 +337,17 @@ async fn router_handle_response(
// Check if this live query is registered
if let Some(sender) = state.live_queries.get(&live_query_id) {
// Send the notification back to the caller or kill live query if the receiver is already dropped
let notification = Notification {
query_id: *notification.id,
action: Action::from_core(notification.action),
data: notification.result,
};
if sender.send(notification).await.is_err() {
state.live_queries.remove(&live_query_id);
let kill = {
let request = Command::Kill {
uuid: *live_query_id,
uuid: live_query_id.0,
}
.into_router_request(None)
.unwrap();
@ -364,18 +373,18 @@ async fn router_handle_response(
Err(error) => {
#[revisioned(revision = 1)]
#[derive(Deserialize)]
struct Response {
id: Option<Value>,
struct ErrorResponse {
id: Option<CoreValue>,
}
// Let's try to find out the ID of the response that failed to deserialise
if let Message::Binary(binary) = response {
if let Ok(Response {
if let Ok(ErrorResponse {
id,
}) = deserialize(&mut &binary[..], endpoint.supports_revision)
}) = deserialize(&binary, endpoint.supports_revision)
{
// Return an error if an ID was returned
if let Some(Ok(id)) = id.map(Value::coerce_to_i64) {
if let Some(Ok(id)) = id.map(CoreValue::coerce_to_i64) {
if let Some(pending) = state.pending_requests.remove(&id) {
let _res = pending.response_channel.send(Err(error)).await;
} else {
@ -590,7 +599,7 @@ impl Response {
Ok(None)
}
Message::Binary(binary) => {
deserialize(&mut &binary[..], supports_revision).map(Some).map_err(|error| {
deserialize(binary, supports_revision).map(Some).map_err(|error| {
Error::ResponseFromBinary {
binary: binary.clone(),
error: bincode::ErrorKind::Custom(error.to_string()).into(),

View file

@ -17,7 +17,7 @@ use crate::api::Surreal;
use crate::engine::remote::Data;
use crate::engine::IntervalStream;
use crate::opt::WaitFor;
use crate::sql::Value;
use crate::{Action, Notification};
use channel::{Receiver, Sender};
use futures::stream::{SplitSink, SplitStream};
use futures::FutureExt;
@ -36,6 +36,7 @@ use std::sync::atomic::AtomicI64;
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
use surrealdb_core::sql::Value as CoreValue;
use tokio::sync::watch;
use trice::Instant;
use wasm_bindgen_futures::spawn_local;
@ -137,7 +138,7 @@ async fn router_handle_request(
ref notification_sender,
} => {
state.live_queries.insert(*uuid, notification_sender.clone());
if response.send(Ok(DbResponse::Other(Value::None))).await.is_err() {
if response.send(Ok(DbResponse::Other(CoreValue::None))).await.is_err() {
trace!("Receiver dropped");
}
// There is nothing to send to the server here
@ -225,22 +226,24 @@ async fn router_handle_response(
RequestEffect::None => {}
RequestEffect::Insert => {
// For insert, we need to flatten single responses in an array
if let Ok(Data::Other(Value::Array(value))) =
if let Ok(Data::Other(CoreValue::Array(value))) =
response.result
{
if value.len() == 1 {
let _ = pending
.response_channel
.send(DbResponse::from(Ok(Data::Other(
value.into_iter().next().unwrap(),
))))
.send(DbResponse::from_server_result(Ok(
Data::Other(
value.into_iter().next().unwrap(),
),
)))
.await;
} else {
let _ = pending
.response_channel
.send(DbResponse::from(Ok(Data::Other(
Value::Array(value),
))))
.send(DbResponse::from_server_result(Ok(
Data::Other(CoreValue::Array(value)),
)))
.await;
}
return HandleResult::Ok;
@ -260,7 +263,7 @@ async fn router_handle_response(
}
let _res = pending
.response_channel
.send(DbResponse::from(response.result))
.send(DbResponse::from_server_result(response.result))
.await;
} else {
warn!("got response for request with id '{id}', which was not in pending requests")
@ -274,6 +277,12 @@ async fn router_handle_response(
// Check if this live query is registered
if let Some(sender) = state.live_queries.get(&live_query_id) {
// Send the notification back to the caller or kill live query if the receiver is already dropped
let notification = Notification {
query_id: notification.id.0,
action: Action::from_core(notification.action),
data: notification.result,
};
if sender.send(notification).await.is_err() {
state.live_queries.remove(&live_query_id);
let kill = {
@ -304,7 +313,7 @@ async fn router_handle_response(
#[derive(Deserialize)]
#[revisioned(revision = 1)]
struct Response {
id: Option<Value>,
id: Option<CoreValue>,
}
// Let's try to find out the ID of the response that failed to deserialise
@ -314,7 +323,7 @@ async fn router_handle_response(
}) = deserialize(&mut &binary[..], endpoint.supports_revision)
{
// Return an error if an ID was returned
if let Some(Ok(id)) = id.map(Value::coerce_to_i64) {
if let Some(Ok(id)) = id.map(CoreValue::coerce_to_i64) {
if let Some(req) = state.pending_requests.remove(&id) {
let _res = req.response_channel.send(Err(error)).await;
} else {
@ -434,7 +443,7 @@ pub(crate) async fn run_router(
let ping = {
let mut request = BTreeMap::new();
request.insert("method".to_owned(), "ping".into());
let value = Value::from(request);
let value = CoreValue::from(request);
let value = serialize(&value, endpoint.supports_revision).unwrap();
Message::Binary(value)
};

View file

@ -1,18 +1,17 @@
use crate::engine::IntervalStream;
use crate::kvs::Datastore;
use crate::options::EngineOptions;
#[cfg(not(target_arch = "wasm32"))]
use crate::Error as RootError;
use futures::StreamExt;
#[cfg(target_arch = "wasm32")]
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::spawn as spawn_future;
use surrealdb_core::{kvs::Datastore, options::EngineOptions};
use tokio::sync::oneshot;
#[cfg(not(target_arch = "wasm32"))]
use tokio::task::JoinHandle;
use crate::Error as RootError;
#[cfg(not(target_arch = "wasm32"))]
use tokio::{spawn as spawn_future, task::JoinHandle};
#[cfg(target_arch = "wasm32")]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local as spawn_future;
@ -38,7 +37,7 @@ impl Tasks {
Err(e) => {
error!("Node agent task failed: {}", e);
let inner_err =
crate::err::Error::NodeAgent("node task failed and has been logged");
surrealdb_core::err::Error::NodeAgent("node task failed and has been logged");
return Err(RootError::Db(inner_err));
}
}
@ -65,7 +64,7 @@ pub fn start_tasks(opt: &EngineOptions, dbs: Arc<Datastore>) -> (Tasks, [oneshot
// This function needs to be called before after the dbs::init and before the net::init functions.
// It needs to be before net::init because the net::init function blocks until the web server stops.
fn init(opt: &EngineOptions, dbs: Arc<Datastore>) -> (FutureTask, oneshot::Sender<()>) {
let _init = crate::dbs::LoggingLifecycle::new("node agent initialisation".to_string());
let _init = surrealdb_core::dbs::LoggingLifecycle::new("node agent initialisation".to_string());
let tick_interval = opt.tick_interval;
trace!("Ticker interval is {:?}", tick_interval);
@ -78,7 +77,7 @@ fn init(opt: &EngineOptions, dbs: Arc<Datastore>) -> (FutureTask, oneshot::Sende
let (tx, mut rx) = oneshot::channel();
let _fut = spawn_future(async move {
let _lifecycle = crate::dbs::LoggingLifecycle::new("heartbeat task".to_string());
let _lifecycle = surrealdb_core::dbs::LoggingLifecycle::new("heartbeat task".to_string());
let mut ticker = interval_ticker(tick_interval).await;
loop {
@ -125,10 +124,9 @@ async fn interval_ticker(interval: Duration) -> IntervalStream {
#[cfg(feature = "kv-mem")]
mod test {
use crate::engine::tasks::start_tasks;
use crate::kvs::Datastore;
use crate::options::EngineOptions;
use std::sync::Arc;
use std::time::Duration;
use surrealdb_core::{kvs::Datastore, options::EngineOptions};
#[test_log::test(tokio::test)]
pub async fn tasks_complete() {

View file

@ -1,12 +1,7 @@
use crate::api::Response;
use crate::sql::Array;
use crate::sql::Edges;
use crate::sql::Object;
use crate::sql::Thing;
use crate::sql::Value;
use crate::{api::Response, Value};
use serde::Serialize;
use std::io;
use std::path::PathBuf;
use std::{convert::Infallible, io};
use surrealdb_core::dbs::capabilities::{ParseFuncTargetError, ParseNetTargetError};
use thiserror::Error;
@ -43,26 +38,29 @@ pub enum Error {
InvalidBindings(Value),
/// Tried to use a range query on a record ID
#[error("Range on record IDs not supported: {0}")]
RangeOnRecordId(Thing),
#[error("Tried to add a range to an record-id resource")]
RangeOnRecordId,
/// Tried to use a range query on an object
#[error("Range on objects not supported: {0}")]
RangeOnObject(Object),
#[error("Tried to add a range to an object resource")]
RangeOnObject,
/// Tried to use a range query on an array
#[error("Range on arrays not supported: {0}")]
RangeOnArray(Array),
#[error("Tried to add a range to an array resource")]
RangeOnArray,
/// Tried to use a range query on an edge or edges
#[error("Range on edges not supported: {0}")]
RangeOnEdges(Edges),
#[error("Tried to add a range to an edge resource")]
RangeOnEdges,
/// Tried to use a range query on an existing range
#[error("Tried to add a range to an resource which was already a range")]
RangeOnRange,
/// Tried to use `table:id` syntax as a method parameter when `(table, id)` should be used instead
#[error("`{table}:{id}` is not allowed as a method parameter; try `({table}, {id})`")]
#[error("Table name `{table}` contained a colon (:), this is dissallowed to avoid confusion with record-id's try `Table(\"{table}\")` instead.")]
TableColonId {
table: String,
id: String,
},
/// Duplicate request ID
@ -172,16 +170,16 @@ pub enum Error {
LiveQueriesNotSupported,
/// Tried to use a range query on an object
#[error("Live queries on objects not supported: {0}")]
LiveOnObject(Object),
#[error("Live queries on objects not supported")]
LiveOnObject,
/// Tried to use a range query on an array
#[error("Live queries on arrays not supported: {0}")]
LiveOnArray(Array),
#[error("Live queries on arrays not supported")]
LiveOnArray,
/// Tried to use a range query on an edge or edges
#[error("Live queries on edges not supported: {0}")]
LiveOnEdges(Edges),
#[error("Live queries on edges not supported")]
LiveOnEdges,
/// Tried to access a query statement as a live query when it isn't a live query
#[error("Query statement {0} is not a live query")]
@ -196,22 +194,64 @@ pub enum Error {
ResponseAlreadyTaken,
/// Tried to insert on an object
#[error("Insert queries on objects not supported: {0}")]
InsertOnObject(Object),
#[error("Insert queries on objects are not supported")]
InsertOnObject,
/// Tried to insert on an array
#[error("Insert queries on arrays not supported: {0}")]
InsertOnArray(Array),
#[error("Insert queries on arrays are not supported")]
InsertOnArray,
/// Tried to insert on an edge or edges
#[error("Insert queries on edges not supported: {0}")]
InsertOnEdges(Edges),
#[error("Insert queries on edges are not supported")]
InsertOnEdges,
/// Tried to insert on an edge or edges
#[error("Insert queries on ranges are not supported")]
InsertOnRange,
#[error("{0}")]
InvalidNetTarget(#[from] ParseNetTargetError),
#[error("{0}")]
InvalidFuncTarget(#[from] ParseFuncTargetError),
#[error("failed to serialize Value: {0}")]
SerializeValue(String),
#[error("failed to deserialize Value: {0}")]
DeSerializeValue(String),
#[error("failed to serialize to a Value: {0}")]
Serializer(String),
#[error("failed to deserialize from a Value: {0}")]
Deserializer(String),
/// Tried to convert an value which contained something like for example a query or future.
#[error("tried to convert from a value which contained non-primitive values to a value which only allows primitive values.")]
RecievedInvalidValue,
}
impl serde::ser::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: std::fmt::Display,
{
Error::SerializeValue(msg.to_string())
}
}
impl serde::de::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: std::fmt::Display,
{
Error::DeSerializeValue(msg.to_string())
}
}
impl From<Infallible> for crate::Error {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl From<ParseNetTargetError> for crate::Error {

View file

@ -4,9 +4,9 @@ use crate::api::method::Commit;
use crate::api::Connection;
use crate::api::Result;
use crate::api::Surreal;
use crate::sql::statements::BeginStatement;
use std::future::IntoFuture;
use std::ops::Deref;
use surrealdb_core::sql::statements::BeginStatement;
/// A beginning of a transaction
#[derive(Debug)]

View file

@ -2,8 +2,8 @@ use crate::api::method::BoxFuture;
use crate::api::Connection;
use crate::api::Result;
use crate::api::Surreal;
use crate::sql::statements::CancelStatement;
use std::future::IntoFuture;
use surrealdb_core::sql::statements::CancelStatement;
/// A transaction cancellation future
#[derive(Debug)]

View file

@ -2,8 +2,8 @@ use crate::api::method::BoxFuture;
use crate::api::Connection;
use crate::api::Result;
use crate::api::Surreal;
use crate::sql::statements::CommitStatement;
use std::future::IntoFuture;
use surrealdb_core::sql::statements::CommitStatement;
/// A transaction commit future
#[derive(Debug)]

View file

@ -3,8 +3,8 @@ use crate::api::method::BoxFuture;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Value;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use std::borrow::Cow;
use std::future::IntoFuture;

View file

@ -4,14 +4,14 @@ use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Value;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::Cow;
use std::future::IntoFuture;
use std::marker::PhantomData;
use surrealdb_core::sql::to_value;
use surrealdb_core::sql::{to_value as to_core_value, Value as CoreValue};
use super::Content;
@ -48,7 +48,7 @@ macro_rules! into_future {
Box::pin(async move {
let router = client.router.extract()?;
let cmd = Command::Create {
what: resource?.into(),
what: resource?,
data: None,
};
router.$method(cmd).await
@ -99,15 +99,15 @@ where
D: Serialize + 'static,
{
Content::from_closure(self.client, || {
let content = to_value(data)?;
let content = to_core_value(data)?;
let data = match content {
Value::None | Value::Null => None,
CoreValue::None | CoreValue::Null => None,
content => Some(content),
};
Ok(Command::Create {
what: self.resource?.into(),
what: self.resource?,
data,
})
})

View file

@ -1,13 +1,12 @@
use crate::api::conn::Command;
use crate::api::method::BoxFuture;
use crate::api::opt::Range;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Id;
use crate::sql::Value;
use crate::opt::KeyRange;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use std::borrow::Cow;
use std::future::IntoFuture;
@ -19,7 +18,6 @@ use std::marker::PhantomData;
pub struct Delete<'r, C: Connection, R> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) resource: Result<Resource>,
pub(super) range: Option<Range<Id>>,
pub(super) response_type: PhantomData<R>,
}
@ -42,18 +40,13 @@ macro_rules! into_future {
let Delete {
client,
resource,
range,
..
} = self;
Box::pin(async move {
let param: Value = match range {
Some(range) => resource?.with_range(range)?.into(),
None => resource?.into(),
};
let router = client.router.extract()?;
router
.$method(Command::Delete {
what: param,
what: resource?,
})
.await
})
@ -98,8 +91,8 @@ where
C: Connection,
{
/// Restricts a range of records to delete
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -109,8 +102,8 @@ where
C: Connection,
{
/// Restricts a range of records to delete
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}

View file

@ -6,16 +6,14 @@ use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Ident;
use crate::sql::Part;
use crate::sql::Table;
use crate::sql::Value;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::Cow;
use std::future::IntoFuture;
use std::marker::PhantomData;
use surrealdb_core::sql::{to_value as to_core_value, Object as CoreObject, Value as CoreValue};
/// An insert future
#[derive(Debug)]
@ -49,19 +47,25 @@ macro_rules! into_future {
} = self;
Box::pin(async move {
let (table, data) = match resource? {
Resource::Table(table) => (table.into(), Value::Object(Default::default())),
Resource::Table(table) => (table.into(), CoreObject::default()),
Resource::RecordId(record_id) => {
let mut table = Table::default();
table.0 = record_id.tb.clone();
(table.into(), map! { String::from("id") => record_id.into() }.into())
let record_id = record_id.into_inner();
let mut map = CoreObject::default();
map.insert("id".to_string(), record_id.id.into());
(record_id.tb, map)
}
Resource::Object(obj) => return Err(Error::InsertOnObject(obj).into()),
Resource::Array(arr) => return Err(Error::InsertOnArray(arr).into()),
Resource::Edges(edges) => return Err(Error::InsertOnEdges(edges).into()),
Resource::Object(_) => return Err(Error::InsertOnObject.into()),
Resource::Array(_) => return Err(Error::InsertOnArray.into()),
Resource::Edge {
..
} => return Err(Error::InsertOnEdges.into()),
Resource::Range {
..
} => return Err(Error::InsertOnRange.into()),
};
let cmd = Command::Insert {
what: Some(table),
data,
what: table.to_string(),
data: data.into(),
};
let router = client.router.extract()?;
@ -114,10 +118,10 @@ where
D: Serialize + 'static,
{
Content::from_closure(self.client, || {
let mut data = crate::sql::to_value(data)?;
let mut data = to_core_value(data)?;
match self.resource? {
Resource::Table(table) => Ok(Command::Insert {
what: Some(table.into()),
what: table,
data,
}),
Resource::RecordId(thing) => {
@ -127,22 +131,21 @@ where
)
.into())
} else {
let mut table = Table::default();
table.0.clone_from(&thing.tb);
let what = Value::Table(table);
let mut ident = Ident::default();
"id".clone_into(&mut ident.0);
let id = Part::Field(ident);
data.put(&[id], thing.into());
let thing = thing.into_inner();
if let CoreValue::Object(ref mut x) = data {
x.insert("id".to_string(), thing.id.into());
}
Ok(Command::Insert {
what: Some(what),
what: thing.tb,
data,
})
}
}
Resource::Object(obj) => Err(Error::InsertOnObject(obj).into()),
Resource::Array(arr) => Err(Error::InsertOnArray(arr).into()),
Resource::Edges(edges) => Err(Error::InsertOnEdges(edges).into()),
Resource::Object(_) => Err(Error::InsertOnObject.into()),
Resource::Array(_) => Err(Error::InsertOnArray.into()),
Resource::Edge(_) => Err(Error::InsertOnEdges.into()),
Resource::Range(_) => Err(Error::InsertOnRange.into()),
}
})
}

View file

@ -5,25 +5,15 @@ use crate::api::method::BoxFuture;
use crate::api::Connection;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::dbs;
use crate::engine::any::Any;
use crate::method::Live;
use crate::method::OnceLockExt;
use crate::method::Query;
use crate::method::Select;
use crate::opt::Resource;
use crate::sql::from_value;
use crate::sql::statements::LiveStatement;
use crate::sql::Field;
use crate::sql::Fields;
use crate::sql::Ident;
use crate::sql::Idiom;
use crate::sql::Part;
use crate::sql::Statement;
use crate::sql::Table;
use crate::sql::Value;
use crate::Notification;
use crate::value::Notification;
use crate::Surreal;
use crate::Value;
use channel::Receiver;
use futures::StreamExt;
use serde::de::DeserializeOwned;
@ -32,81 +22,91 @@ use std::marker::PhantomData;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
use surrealdb_core::sql::{
statements::LiveStatement, Cond, Expression, Field, Fields, Ident, Idiom, Operator, Part,
Statement, Table, Value as CoreValue,
};
use uuid::Uuid;
#[cfg(not(target_arch = "wasm32"))]
use tokio::spawn;
use uuid::Uuid;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local as spawn;
const ID: &str = "id";
macro_rules! into_future {
() => {
fn into_future(self) -> Self::IntoFuture {
let Select {
client,
resource,
range,
..
} = self;
Box::pin(async move {
let router = client.router.extract()?;
if !router.features.contains(&ExtraFeatures::LiveQueries) {
return Err(Error::LiveQueriesNotSupported.into());
}
let mut fields = Fields::default();
fields.0 = vec![Field::All];
let mut stmt = LiveStatement::new(fields);
let mut table = Table::default();
match range {
Some(range) => {
let record = resource?.with_range(range)?;
table.0 = record.tb.clone();
stmt.what = table.into();
stmt.cond = record.to_cond();
}
None => match resource? {
Resource::Table(table) => {
stmt.what = table.into();
}
Resource::RecordId(record) => {
table.0 = record.tb.clone();
stmt.what = table.into();
let mut ident = Ident::default();
ident.0 = ID.to_owned();
let mut idiom = Idiom::default();
idiom.0 = vec![Part::from(ident)];
stmt.cond = record.to_cond();
}
Resource::Object(object) => return Err(Error::LiveOnObject(object).into()),
Resource::Array(array) => return Err(Error::LiveOnArray(array).into()),
Resource::Edges(edges) => return Err(Error::LiveOnEdges(edges).into()),
},
}
let query = Query::new(
client.clone(),
vec![Statement::Live(stmt)],
Default::default(),
false,
);
let Value::Uuid(id) = query.await?.take(0)? else {
return Err(Error::InternalError(
"successufull live query didn't return a uuid".to_string(),
)
.into());
};
let rx = register(router, id.0).await?;
Ok(Stream::new(
Surreal::new_from_router_waiter(client.router.clone(), client.waiter.clone()),
id.0,
Some(rx),
))
})
fn into_future<C, O>(this: Select<C, O, Live>) -> BoxFuture<Result<Stream<O>>>
where
C: Connection,
{
let Select {
client,
resource,
..
} = this;
Box::pin(async move {
let router = client.router.extract()?;
if !router.features.contains(&ExtraFeatures::LiveQueries) {
return Err(Error::LiveQueriesNotSupported.into());
}
};
let mut fields = Fields::default();
fields.0 = vec![Field::All];
let mut stmt = LiveStatement::new(fields);
let mut table = Table::default();
match resource? {
Resource::Table(table) => {
let mut core_table = Table::default();
core_table.0 = table;
stmt.what = core_table.into()
}
Resource::RecordId(record) => {
let record = record.into_inner();
table.0 = record.tb.clone();
stmt.what = table.into();
let mut ident = Ident::default();
ident.0 = ID.to_owned();
let mut idiom = Idiom::default();
idiom.0 = vec![Part::from(ident)];
let mut cond = Cond::default();
cond.0 = surrealdb_core::sql::Value::Expression(Box::new(Expression::new(
idiom.into(),
Operator::Equal,
record.into(),
)));
stmt.cond = Some(cond);
}
Resource::Object(_) => return Err(Error::LiveOnObject.into()),
Resource::Array(_) => return Err(Error::LiveOnArray.into()),
Resource::Edge(_) => return Err(Error::LiveOnEdges.into()),
Resource::Range(range) => {
let range = range.into_inner();
table.0 = range.tb.clone();
stmt.what = table.into();
stmt.cond = range.to_cond();
}
}
let query =
Query::new(client.clone(), vec![Statement::Live(stmt)], Default::default(), false);
let CoreValue::Uuid(id) = query.await?.take::<Value>(0)?.into_inner() else {
return Err(Error::InternalError(
"successufull live query didn't return a uuid".to_string(),
)
.into());
};
let rx = register(router, *id).await?;
Ok(Stream::new(
Surreal::new_from_router_waiter(client.router.clone(), client.waiter.clone()),
*id,
Some(rx),
))
})
}
pub(crate) async fn register(router: &Router, id: Uuid) -> Result<Receiver<dbs::Notification>> {
pub(crate) async fn register(
router: &Router,
id: Uuid,
) -> Result<Receiver<Notification<CoreValue>>> {
let (tx, rx) = channel::unbounded();
router
.execute_unit(Command::SubscribeLive {
@ -124,7 +124,9 @@ where
type Output = Result<Stream<Value>>;
type IntoFuture = BoxFuture<'r, Self::Output>;
into_future! {}
fn into_future(self) -> Self::IntoFuture {
into_future(self)
}
}
impl<'r, Client, R> IntoFuture for Select<'r, Client, Option<R>, Live>
@ -135,7 +137,9 @@ where
type Output = Result<Stream<Option<R>>>;
type IntoFuture = BoxFuture<'r, Self::Output>;
into_future! {}
fn into_future(self) -> Self::IntoFuture {
into_future(self)
}
}
impl<'r, Client, R> IntoFuture for Select<'r, Client, Vec<R>, Live>
@ -146,7 +150,9 @@ where
type Output = Result<Stream<Vec<R>>>;
type IntoFuture = BoxFuture<'r, Self::Output>;
into_future! {}
fn into_future(self) -> Self::IntoFuture {
into_future(self)
}
}
/// A stream of live query notifications
@ -157,7 +163,7 @@ pub struct Stream<R> {
// We no longer need the lifetime and the type parameter
// Leaving them in for backwards compatibility
pub(crate) id: Uuid,
pub(crate) rx: Option<Receiver<dbs::Notification>>,
pub(crate) rx: Option<Receiver<Notification<CoreValue>>>,
pub(crate) response_type: PhantomData<R>,
}
@ -165,7 +171,7 @@ impl<R> Stream<R> {
pub(crate) fn new(
client: Surreal<Any>,
id: Uuid,
rx: Option<Receiver<dbs::Notification>>,
rx: Option<Receiver<Notification<CoreValue>>>,
) -> Self {
Self {
id,
@ -195,23 +201,22 @@ impl futures::Stream for Stream<Value> {
type Item = Notification<Value>;
poll_next! {
notification => Poll::Ready(Some(Notification {
query_id: notification.id.0,
action: notification.action.into(),
data: notification.result,
}))
notification => {
let r = Notification{
query_id: notification.query_id,
action: notification.action,
data: Value::from_inner(notification.data),
};
Poll::Ready(Some(r))
}
}
}
macro_rules! poll_next_and_convert {
() => {
poll_next! {
notification => match from_value(notification.result) {
Ok(data) => Poll::Ready(Some(Ok(Notification {
data,
query_id: notification.id.0,
action: notification.action.into(),
}))),
notification => match notification.map_deserialize(){
Ok(data) => Poll::Ready(Some(Ok(data))),
Err(error) => Poll::Ready(Some(Err(error.into()))),
}
}

View file

@ -1,19 +1,17 @@
use crate::api::conn::Command;
use crate::api::method::BoxFuture;
use crate::api::opt::Range;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Value;
use crate::value::Value;
use crate::Surreal;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::Cow;
use std::future::IntoFuture;
use std::marker::PhantomData;
use surrealdb_core::sql::{to_value as to_core_value, Value as CoreValue};
/// A merge future
#[derive(Debug)]
@ -21,7 +19,6 @@ use std::marker::PhantomData;
pub struct Merge<'r, C: Connection, D, R> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) resource: Result<Resource>,
pub(super) range: Option<Range<Id>>,
pub(super) content: D,
pub(super) response_type: PhantomData<R>,
}
@ -45,25 +42,19 @@ macro_rules! into_future {
let Merge {
client,
resource,
range,
content,
..
} = self;
let content = to_value(content);
let content = to_core_value(content);
Box::pin(async move {
let param: Value = match range {
Some(range) => resource?.with_range(range)?.into(),
None => resource?.into(),
};
let content = match content? {
Value::None | Value::Null => None,
CoreValue::None | CoreValue::Null => None,
x => Some(x),
};
let router = client.router.extract()?;
let cmd = Command::Merge {
what: param,
what: resource?,
data: content,
};
router.$method(cmd).await

View file

@ -11,8 +11,6 @@ use crate::api::OnceLockExt;
use crate::api::Surreal;
use crate::opt::IntoExportDestination;
use crate::opt::WaitFor;
use crate::sql::to_value;
use run::IntoArgs;
use serde::Serialize;
use std::borrow::Cow;
use std::marker::PhantomData;
@ -21,6 +19,7 @@ use std::pin::Pin;
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
use surrealdb_core::sql::{to_value as to_core_value, Array as CoreArray};
pub(crate) mod live;
pub(crate) mod query;
@ -77,8 +76,8 @@ pub use merge::Merge;
pub use patch::Patch;
pub use query::Query;
pub use query::QueryStream;
use run::IntoFn;
pub use run::Run;
pub use run::{IntoArgs, IntoFn};
pub use select::Select;
use serde_content::Serializer;
pub use set::Set;
@ -92,6 +91,8 @@ pub use use_db::UseDb;
pub use use_ns::UseNs;
pub use version::Version;
use super::opt::IntoResource;
/// A alias for an often used type of future returned by async methods in this library.
pub(crate) type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + Sync + 'a>>;
@ -329,7 +330,7 @@ where
Set {
client: Cow::Borrowed(self),
key: key.into(),
value: to_value(value).map_err(Into::into),
value: to_core_value(value).map_err(Into::into),
}
}
@ -692,11 +693,10 @@ where
/// # Ok(())
/// # }
/// ```
pub fn select<R>(&self, resource: impl opt::IntoResource<R>) -> Select<C, R> {
pub fn select<O>(&self, resource: impl IntoResource<O>) -> Select<C, O> {
Select {
client: Cow::Borrowed(self),
resource: resource.into_resource(),
range: None,
response_type: PhantomData,
query_type: PhantomData,
}
@ -748,7 +748,7 @@ where
/// # Ok(())
/// # }
/// ```
pub fn create<R>(&self, resource: impl opt::IntoResource<R>) -> Create<C, R> {
pub fn create<R>(&self, resource: impl IntoResource<R>) -> Create<C, R> {
Create {
client: Cow::Borrowed(self),
resource: resource.into_resource(),
@ -849,7 +849,7 @@ where
/// # Ok(())
/// # }
/// ```
pub fn insert<R>(&self, resource: impl opt::IntoResource<R>) -> Insert<C, R> {
pub fn insert<O>(&self, resource: impl IntoResource<O>) -> Insert<C, O> {
Insert {
client: Cow::Borrowed(self),
resource: resource.into_resource(),
@ -1007,11 +1007,10 @@ where
/// # Ok(())
/// # }
/// ```
pub fn upsert<R>(&self, resource: impl opt::IntoResource<R>) -> Upsert<C, R> {
pub fn upsert<O>(&self, resource: impl IntoResource<O>) -> Upsert<C, O> {
Upsert {
client: Cow::Borrowed(self),
resource: resource.into_resource(),
range: None,
response_type: PhantomData,
}
}
@ -1166,11 +1165,10 @@ where
/// # Ok(())
/// # }
/// ```
pub fn update<R>(&self, resource: impl opt::IntoResource<R>) -> Update<C, R> {
pub fn update<O>(&self, resource: impl IntoResource<O>) -> Update<C, O> {
Update {
client: Cow::Borrowed(self),
resource: resource.into_resource(),
range: None,
response_type: PhantomData,
}
}
@ -1199,11 +1197,10 @@ where
/// # Ok(())
/// # }
/// ```
pub fn delete<R>(&self, resource: impl opt::IntoResource<R>) -> Delete<C, R> {
pub fn delete<O>(&self, resource: impl IntoResource<O>) -> Delete<C, O> {
Delete {
client: Cow::Borrowed(self),
resource: resource.into_resource(),
range: None,
response_type: PhantomData,
}
}
@ -1226,14 +1223,18 @@ where
}
}
// TODO: Re-enable doc tests
/// Runs a function
///
/// # Examples
///
/// ```no_run
/// ```ignore
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
/// # let db = surrealdb::engine::any::connect("mem://").await?;
/// // Note that the sdk is currently undergoing some changes so the below examples might not
/// work until the sdk is somewhat more stable.
///
/// // specify no args with an empty tuple, vec, or slice
/// let foo = db.run("fn::foo", ()).await?; // fn::foo()
/// // a single value will be turned into one arguement unless it is a tuple or vec
@ -1251,11 +1252,13 @@ where
///
pub fn run(&self, name: impl IntoFn, args: impl IntoArgs) -> Run<C> {
let (name, version) = name.into_fn();
let mut arguments = CoreArray::default();
arguments.0 = crate::Value::array_to_core(args.into_args());
Run {
client: Cow::Borrowed(self),
name,
version,
args: args.into_args(),
args: arguments,
}
}

View file

@ -1,20 +1,18 @@
use crate::api::conn::Command;
use crate::api::method::BoxFuture;
use crate::api::opt::PatchOp;
use crate::api::opt::Range;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Value;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use serde_content::Value as Content;
use std::borrow::Cow;
use std::future::IntoFuture;
use std::marker::PhantomData;
use surrealdb_core::sql::{to_value as to_core_value, Value as CoreValue};
/// A patch future
#[derive(Debug)]
@ -22,7 +20,6 @@ use std::marker::PhantomData;
pub struct Patch<'r, C: Connection, R> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) resource: Result<Resource>,
pub(super) range: Option<Range<Id>>,
pub(super) patches: Vec<serde_content::Result<Content<'static>>>,
pub(super) response_type: PhantomData<R>,
}
@ -46,25 +43,20 @@ macro_rules! into_future {
let Patch {
client,
resource,
range,
patches,
..
} = self;
Box::pin(async move {
let param: Value = match range {
Some(range) => resource?.with_range(range)?.into(),
None => resource?.into(),
};
let mut vec = Vec::with_capacity(patches.len());
for result in patches {
let content = result.map_err(crate::error::Db::from)?;
let value = to_value(content)?;
let value = to_core_value(content)?;
vec.push(value);
}
let patches = Value::from(vec);
let patches = CoreValue::from(vec);
let router = client.router.extract()?;
let cmd = Command::Patch {
what: param,
what: resource?,
data: Some(patches),
};

View file

@ -1,6 +1,4 @@
use super::live;
use super::Stream;
use super::{live, Stream};
use crate::api::conn::Command;
use crate::api::err::Error;
use crate::api::method::BoxFuture;
@ -12,12 +10,8 @@ use crate::engine::any::Any;
use crate::method::OnceLockExt;
use crate::method::Stats;
use crate::method::WithStats;
use crate::sql;
use crate::sql::to_value;
use crate::sql::Statement;
use crate::sql::Value;
use crate::Notification;
use crate::Surreal;
use crate::value::Notification;
use crate::{Surreal, Value};
use futures::future::Either;
use futures::stream::SelectAll;
use futures::StreamExt;
@ -25,13 +19,14 @@ use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::future::IntoFuture;
use std::mem;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
use surrealdb_core::sql::{
self, to_value as to_core_value, Object as CoreObject, Statement, Value as CoreValue,
};
/// A query future
#[derive(Debug)]
@ -44,7 +39,7 @@ pub struct Query<'r, C: Connection> {
pub(crate) struct ValidQuery<'r, C: Connection> {
pub client: Cow<'r, Surreal<C>>,
pub query: Vec<Statement>,
pub bindings: BTreeMap<String, Value>,
pub bindings: CoreObject,
pub register_live_queries: bool,
}
@ -55,7 +50,7 @@ where
pub(crate) fn new(
client: Cow<'r, Surreal<C>>,
query: Vec<Statement>,
bindings: BTreeMap<String, Value>,
bindings: CoreObject,
register_live_queries: bool,
) -> Self {
Query {
@ -173,7 +168,7 @@ where
// creating another public error variant for this internal error.
let res = match result {
Ok(id) => {
let Value::Uuid(uuid) = id else {
let CoreValue::Uuid(uuid) = id else {
return Err(Error::InternalError(
"successfull live query did not return a uuid".to_string(),
)
@ -278,17 +273,28 @@ where
/// ```
pub fn bind(self, bindings: impl Serialize + 'static) -> Self {
self.map_valid(move |mut valid| {
let mut bindings = to_value(bindings)?;
if let Value::Array(array) = &mut bindings {
if let [Value::Strand(key), value] = &mut array.0[..] {
let mut map = BTreeMap::new();
map.insert(mem::take(&mut key.0), mem::take(value));
bindings = map.into();
let bindings = to_core_value(bindings)?;
match bindings {
CoreValue::Object(mut map) => valid.bindings.append(&mut map.0),
CoreValue::Array(array) => {
if array.len() != 2 || !matches!(array[0], CoreValue::Strand(_)) {
let bindings = CoreValue::Array(array);
let bindings = Value::from_inner(bindings);
return Err(Error::InvalidBindings(bindings).into());
}
let mut iter = array.into_iter();
let Some(CoreValue::Strand(key)) = iter.next() else {
unreachable!()
};
let Some(value) = iter.next() else {
unreachable!()
};
valid.bindings.0.insert(key.0, value);
}
}
match &mut bindings {
Value::Object(map) => valid.bindings.append(&mut map.0),
_ => {
let bindings = Value::from_inner(bindings);
return Err(Error::InvalidBindings(bindings).into());
}
}
@ -298,7 +304,7 @@ where
}
}
pub(crate) type QueryResult = Result<Value>;
pub(crate) type QueryResult = Result<CoreValue>;
/// The response type of a `Surreal::query` request
#[derive(Debug)]
@ -419,7 +425,7 @@ impl Response {
/// ```no_run
/// use serde::Deserialize;
/// use surrealdb::Notification;
/// use surrealdb::sql::Value;
/// use surrealdb::Value;
///
/// #[derive(Debug, Deserialize)]
/// # #[allow(dead_code)]
@ -464,7 +470,6 @@ impl Response {
/// # Examples
///
/// ```no_run
/// use surrealdb::sql;
///
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
@ -495,7 +500,6 @@ impl Response {
/// # Examples
///
/// ```no_run
/// use surrealdb::sql;
///
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
@ -526,7 +530,6 @@ impl Response {
/// # Examples
///
/// ```no_run
/// use surrealdb::sql;
///
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
@ -553,7 +556,6 @@ impl WithStats<Response> {
///
/// ```no_run
/// use serde::Deserialize;
/// use surrealdb::sql;
///
/// #[derive(Debug, Deserialize)]
/// # #[allow(dead_code)]
@ -623,7 +625,6 @@ impl WithStats<Response> {
/// # Examples
///
/// ```no_run
/// use surrealdb::sql;
///
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
@ -654,7 +655,6 @@ impl WithStats<Response> {
/// # Examples
///
/// ```no_run
/// use surrealdb::sql;
///
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
@ -674,7 +674,6 @@ impl WithStats<Response> {
/// # Examples
///
/// ```no_run
/// use surrealdb::sql;
///
/// # #[tokio::main]
/// # async fn main() -> surrealdb::Result<()> {
@ -698,8 +697,9 @@ impl WithStats<Response> {
#[cfg(test)]
mod tests {
use super::*;
use crate::Error::Api;
use crate::{value::to_value, Error::Api};
use serde::Deserialize;
use surrealdb_core::sql::Value as CoreValue;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Summary {
@ -728,7 +728,7 @@ mod tests {
fn take_from_an_empty_response() {
let mut response = Response::new();
let value: Value = response.take(0).unwrap();
assert!(value.is_none());
assert!(value.into_inner().is_none());
let mut response = Response::new();
let option: Option<String> = response.take(0).unwrap();
@ -781,7 +781,7 @@ mod tests {
..Response::new()
};
let value: Value = response.take(0).unwrap();
assert_eq!(value, Value::from(scalar));
assert_eq!(value.into_inner(), CoreValue::from(scalar));
let mut response = Response {
results: to_map(vec![Ok(scalar.into())]),
@ -794,7 +794,7 @@ mod tests {
results: to_map(vec![Ok(scalar.into())]),
..Response::new()
};
let vec: Vec<usize> = response.take(0).unwrap();
let vec: Vec<i64> = response.take(0).unwrap();
assert_eq!(vec, vec![scalar]);
let scalar = true;
@ -804,7 +804,7 @@ mod tests {
..Response::new()
};
let value: Value = response.take(0).unwrap();
assert_eq!(value, Value::from(scalar));
assert_eq!(value.into_inner(), CoreValue::from(scalar));
let mut response = Response {
results: to_map(vec![Ok(scalar.into())]),
@ -849,7 +849,7 @@ mod tests {
};
assert_eq!(zero, 0);
let one: Value = response.take(1).unwrap();
assert_eq!(one, Value::from(1));
assert_eq!(one.into_inner(), CoreValue::from(1));
}
#[test]
@ -860,14 +860,14 @@ mod tests {
let value = to_value(summary.clone()).unwrap();
let mut response = Response {
results: to_map(vec![Ok(value.clone())]),
results: to_map(vec![Ok(value.clone().into_inner())]),
..Response::new()
};
let title: Value = response.take("title").unwrap();
assert_eq!(title, Value::from(summary.title.as_str()));
assert_eq!(title.into_inner(), CoreValue::from(summary.title.as_str()));
let mut response = Response {
results: to_map(vec![Ok(value.clone())]),
results: to_map(vec![Ok(value.clone().into_inner())]),
..Response::new()
};
let Some(title): Option<String> = response.take("title").unwrap() else {
@ -876,7 +876,7 @@ mod tests {
assert_eq!(title, summary.title);
let mut response = Response {
results: to_map(vec![Ok(value)]),
results: to_map(vec![Ok(value.into_inner())]),
..Response::new()
};
let vec: Vec<String> = response.take("title").unwrap();
@ -889,7 +889,7 @@ mod tests {
let value = to_value(article.clone()).unwrap();
let mut response = Response {
results: to_map(vec![Ok(value.clone())]),
results: to_map(vec![Ok(value.clone().into_inner())]),
..Response::new()
};
let Some(title): Option<String> = response.take("title").unwrap() else {
@ -902,18 +902,18 @@ mod tests {
assert_eq!(body, article.body);
let mut response = Response {
results: to_map(vec![Ok(value.clone())]),
results: to_map(vec![Ok(value.clone().into_inner())]),
..Response::new()
};
let vec: Vec<String> = response.take("title").unwrap();
assert_eq!(vec, vec![article.title.clone()]);
let mut response = Response {
results: to_map(vec![Ok(value)]),
results: to_map(vec![Ok(value.into_inner())]),
..Response::new()
};
let value: Value = response.take("title").unwrap();
assert_eq!(value, Value::from(article.title));
assert_eq!(value.into_inner(), CoreValue::from(article.title));
}
#[test]
@ -923,7 +923,7 @@ mod tests {
..Response::new()
};
let value: Value = response.take(0).unwrap();
assert_eq!(value, vec![Value::from(true), Value::from(false)].into());
assert_eq!(value.into_inner(), vec![CoreValue::from(true), CoreValue::from(false)].into());
let mut response = Response {
results: to_map(vec![Ok(vec![true, false].into())]),
@ -1008,6 +1008,6 @@ mod tests {
};
assert_eq!(value, 2);
let value: Value = response.take(4).unwrap();
assert_eq!(value, Value::from(3));
assert_eq!(value.into_inner(), CoreValue::from(3));
}
}

View file

@ -2,13 +2,13 @@ use crate::api::conn::Command;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Array;
use crate::sql::Value;
use crate::Surreal;
use crate::Value;
use std::borrow::Cow;
use std::future::Future;
use std::future::IntoFuture;
use std::pin::Pin;
use surrealdb_core::sql::Array as CoreArray;
use super::BoxFuture;
/// A run future
#[derive(Debug)]
@ -17,7 +17,7 @@ pub struct Run<'r, C: Connection> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) name: String,
pub(super) version: Option<String>,
pub(super) args: Array,
pub(super) args: CoreArray,
}
impl<C> Run<'_, C>
where
@ -37,7 +37,7 @@ where
Client: Connection,
{
type Output = Result<Value>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync + 'r>>;
type IntoFuture = BoxFuture<'r, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
let Run {
@ -60,19 +60,12 @@ where
}
pub trait IntoArgs {
fn into_args(self) -> Array;
}
impl IntoArgs for Array {
fn into_args(self) -> Array {
self
}
fn into_args(self) -> Vec<Value>;
}
impl IntoArgs for Value {
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self];
Array::from(arr)
fn into_args(self) -> Vec<Value> {
vec![self]
}
}
@ -80,9 +73,8 @@ impl<T> IntoArgs for Vec<T>
where
T: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.into_iter().map(Into::into).collect();
Array::from(arr)
fn into_args(self) -> Vec<Value> {
self.into_iter().map(Into::into).collect()
}
}
@ -90,9 +82,8 @@ impl<T, const N: usize> IntoArgs for [T; N]
where
T: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.into_iter().map(Into::into).collect();
Array::from(arr)
fn into_args(self) -> Vec<Value> {
self.into_iter().map(Into::into).collect()
}
}
@ -100,9 +91,8 @@ impl<T, const N: usize> IntoArgs for &[T; N]
where
T: Into<Value> + Clone,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.iter().cloned().map(Into::into).collect();
Array::from(arr)
fn into_args(self) -> Vec<Value> {
self.iter().cloned().map(Into::into).collect()
}
}
@ -110,56 +100,48 @@ impl<T> IntoArgs for &[T]
where
T: Into<Value> + Clone,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = self.iter().cloned().map(Into::into).collect();
Array::from(arr)
}
}
impl IntoArgs for () {
fn into_args(self) -> Array {
Vec::<Value>::new().into()
fn into_args(self) -> Vec<Value> {
self.iter().cloned().map(Into::into).collect()
}
}
impl<T0> IntoArgs for (T0,)
where
T0: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self.0.into()];
Array::from(arr)
macro_rules! impl_args_tuple {
($($i:ident), *$(,)?) => {
impl_args_tuple!(@marker $($i,)*);
};
($($cur:ident,)* @marker $head:ident, $($tail:ident,)*) => {
impl<$($cur: Into<Value>,)*> IntoArgs for ($($cur,)*) {
#[allow(non_snake_case)]
fn into_args(self) -> Vec<Value> {
let ($($cur,)*) = self;
vec![$($cur.into(),)*]
}
}
impl_args_tuple!($($cur,)* $head, @marker $($tail,)*);
};
($($cur:ident,)* @marker ) => {
impl<$($cur: Into<Value>,)*> IntoArgs for ($($cur,)*) {
#[allow(non_snake_case)]
fn into_args(self) -> Vec<Value> {
let ($($cur,)*) = self;
vec![$($cur.into(),)*]
}
}
}
}
impl<T0, T1> IntoArgs for (T0, T1)
where
T0: Into<Value>,
T1: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self.0.into(), self.1.into()];
Array::from(arr)
}
}
impl<T0, T1, T2> IntoArgs for (T0, T1, T2)
where
T0: Into<Value>,
T1: Into<Value>,
T2: Into<Value>,
{
fn into_args(self) -> Array {
let arr: Vec<Value> = vec![self.0.into(), self.1.into(), self.2.into()];
Array::from(arr)
}
}
impl_args_tuple!(A, B, C, D, E, F,);
/* TODO: Removed for now.
* The detach value PR removed a lot of conversion methods with, pending later request which might
* add them back depending on how the sdk turns out.
*
macro_rules! into_impl {
($type:ty) => {
impl IntoArgs for $type {
fn into_args(self) -> Array {
let val: Value = self.into();
Array::from(val)
fn into_args(self) -> Vec<Value> {
vec![Value::from(self)]
}
}
};
@ -180,6 +162,7 @@ into_impl!(f32);
into_impl!(f64);
into_impl!(String);
into_impl!(&str);
*/
pub trait IntoFn {
fn into_fn(self) -> (String, Option<String>);

View file

@ -1,14 +1,13 @@
use crate::api::conn::Command;
use crate::api::method::BoxFuture;
use crate::api::method::OnceLockExt;
use crate::api::opt::Range;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::Live;
use crate::sql::Id;
use crate::sql::Value;
use crate::opt::KeyRange;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use std::borrow::Cow;
use std::future::IntoFuture;
@ -20,7 +19,6 @@ use std::marker::PhantomData;
pub struct Select<'r, C: Connection, R, T = ()> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) resource: Result<Resource>,
pub(super) range: Option<Range<Id>>,
pub(super) response_type: PhantomData<R>,
pub(super) query_type: PhantomData<T>,
}
@ -44,18 +42,13 @@ macro_rules! into_future {
let Select {
client,
resource,
range,
..
} = self;
Box::pin(async move {
let param: Value = match range {
Some(range) => resource?.with_range(range)?.into(),
None => resource?.into(),
};
let router = client.router.extract()?;
router
.$method(Command::Select {
what: param,
what: resource?,
})
.await
})
@ -100,8 +93,8 @@ where
C: Connection,
{
/// Restricts the records selected to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -111,8 +104,8 @@ where
C: Connection,
{
/// Restricts the records selected to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -173,7 +166,6 @@ where
Select {
client: self.client,
resource: self.resource,
range: self.range,
response_type: self.response_type,
query_type: PhantomData,
}

View file

@ -3,10 +3,10 @@ use crate::api::method::BoxFuture;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::Value;
use crate::Surreal;
use std::borrow::Cow;
use std::future::IntoFuture;
use surrealdb_core::sql::Value as CoreValue;
/// A set future
#[derive(Debug)]
@ -14,7 +14,7 @@ use std::future::IntoFuture;
pub struct Set<'r, C: Connection> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) key: String,
pub(super) value: Result<Value>,
pub(super) value: Result<CoreValue>,
}
impl<C> Set<'_, C>

View file

@ -14,14 +14,13 @@ use crate::api::opt::auth::Root;
use crate::api::opt::PatchOp;
use crate::api::Response as QueryResponse;
use crate::api::Surreal;
use crate::sql::statements::BeginStatement;
use crate::sql::statements::CommitStatement;
use crate::Value;
use once_cell::sync::Lazy;
use protocol::Client;
use protocol::Test;
use semver::Version;
use std::ops::Bound;
use surrealdb_core::sql::Value;
use surrealdb_core::sql::statements::{BeginStatement, CommitStatement};
use types::User;
use types::USER;

View file

@ -1,13 +1,9 @@
use super::types::User;
use crate::api::conn::Command;
use crate::api::conn::DbResponse;
use crate::api::conn::Route;
use crate::api::conn::{Command, DbResponse, Route};
use crate::api::Response as QueryResponse;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Thing;
use crate::sql::Value;
use crate::opt::Resource;
use channel::Receiver;
use surrealdb_core::sql::{to_value as to_core_value, Value as CoreValue};
pub(super) fn mock(route_rx: Receiver<Route>) {
tokio::spawn(async move {
@ -19,7 +15,7 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
let cmd = request.command;
let result = match cmd {
Command::Invalidate | Command::Health => Ok(DbResponse::Other(Value::None)),
Command::Invalidate | Command::Health => Ok(DbResponse::Other(CoreValue::None)),
Command::Authenticate {
..
}
@ -28,14 +24,14 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
}
| Command::Unset {
..
} => Ok(DbResponse::Other(Value::None)),
} => Ok(DbResponse::Other(CoreValue::None)),
Command::SubscribeLive {
..
} => Ok(DbResponse::Other("c6c0e36c-e2cf-42cb-b2d5-75415249b261".to_owned().into())),
Command::Version => Ok(DbResponse::Other("1.0.0".into())),
Command::Use {
..
} => Ok(DbResponse::Other(Value::None)),
} => Ok(DbResponse::Other(CoreValue::None)),
Command::Signup {
..
}
@ -44,7 +40,7 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
} => Ok(DbResponse::Other("jwt".to_owned().into())),
Command::Set {
..
} => Ok(DbResponse::Other(Value::None)),
} => Ok(DbResponse::Other(CoreValue::None)),
Command::Query {
..
} => Ok(DbResponse::Query(QueryResponse::new())),
@ -52,7 +48,7 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
data,
..
} => match data {
None => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
None => Ok(DbResponse::Other(to_core_value(User::default()).unwrap())),
Some(user) => Ok(DbResponse::Other(user.clone())),
},
Command::Select {
@ -63,13 +59,12 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
what,
..
} => match what {
Value::Table(..)
| Value::Array(..)
| Value::Thing(Thing {
id: Id::Range(_),
..
}) => Ok(DbResponse::Other(Value::Array(Default::default()))),
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
Resource::Table(..) | Resource::Array(..) | Resource::Range(_) => {
Ok(DbResponse::Other(CoreValue::Array(Default::default())))
}
Resource::RecordId(..) => {
Ok(DbResponse::Other(to_core_value(User::default()).unwrap()))
}
_ => unreachable!(),
},
Command::Upsert {
@ -88,30 +83,26 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
what,
..
} => match what {
Value::Table(..)
| Value::Array(..)
| Value::Thing(Thing {
id: Id::Range(_),
..
}) => Ok(DbResponse::Other(Value::Array(Default::default()))),
Value::Thing(..) => Ok(DbResponse::Other(to_value(User::default()).unwrap())),
Resource::Table(..) | Resource::Array(..) | Resource::Range(..) => {
Ok(DbResponse::Other(CoreValue::Array(Default::default())))
}
Resource::RecordId(..) => {
Ok(DbResponse::Other(to_core_value(User::default()).unwrap()))
}
_ => unreachable!(),
},
Command::Insert {
what,
data,
} => match (what, data) {
(Some(Value::Table(..)), Value::Array(..)) => {
Ok(DbResponse::Other(Value::Array(Default::default())))
..
} => match data {
CoreValue::Array(..) => {
Ok(DbResponse::Other(CoreValue::Array(Default::default())))
}
(Some(Value::Table(..)), _) => {
Ok(DbResponse::Other(to_value(User::default()).unwrap()))
}
_ => unreachable!(),
_ => Ok(DbResponse::Other(to_core_value(User::default()).unwrap())),
},
Command::Run {
..
} => Ok(DbResponse::Other(Value::None)),
} => Ok(DbResponse::Other(CoreValue::None)),
Command::ExportMl {
..
}
@ -129,7 +120,7 @@ pub(super) fn mock(route_rx: Receiver<Route>) {
}
| Command::ImportFile {
..
} => Ok(DbResponse::Other(Value::None)),
} => Ok(DbResponse::Other(CoreValue::None)),
};
if let Err(message) = response.send(result).await {

View file

@ -4,20 +4,19 @@ use crate::api::method::Content;
use crate::api::method::Merge;
use crate::api::method::Patch;
use crate::api::opt::PatchOp;
use crate::api::opt::Range;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Value;
use crate::opt::KeyRange;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::Cow;
use std::future::IntoFuture;
use std::marker::PhantomData;
use surrealdb_core::sql::{to_value as to_core_value, Value as CoreValue};
/// An update future
#[derive(Debug)]
@ -25,7 +24,6 @@ use std::marker::PhantomData;
pub struct Update<'r, C: Connection, R> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) resource: Result<Resource>,
pub(super) range: Option<Range<Id>>,
pub(super) response_type: PhantomData<R>,
}
@ -48,18 +46,13 @@ macro_rules! into_future {
let Update {
client,
resource,
range,
..
} = self;
Box::pin(async move {
let param: Value = match range {
Some(range) => resource?.with_range(range)?.into(),
None => resource?.into(),
};
let router = client.router.extract()?;
router
.$method(Command::Update {
what: param,
what: resource?,
data: None,
})
.await
@ -105,8 +98,8 @@ where
C: Connection,
{
/// Restricts the records to update to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -116,8 +109,8 @@ where
C: Connection,
{
/// Restricts the records to update to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -133,15 +126,12 @@ where
D: Serialize + 'static,
{
Content::from_closure(self.client, || {
let data = to_value(data)?;
let data = to_core_value(data)?;
let what: Value = match self.range {
Some(range) => self.resource?.with_range(range)?.into(),
None => self.resource?.into(),
};
let what = self.resource?;
let data = match data {
Value::None | Value::Null => None,
CoreValue::None => None,
content => Some(content),
};
@ -160,7 +150,6 @@ where
Merge {
client: self.client,
resource: self.resource,
range: self.range,
content: data,
response_type: PhantomData,
}
@ -171,7 +160,6 @@ where
Patch {
client: self.client,
resource: self.resource,
range: self.range,
patches: vec![patch],
response_type: PhantomData,
}

View file

@ -4,20 +4,19 @@ use crate::api::method::Content;
use crate::api::method::Merge;
use crate::api::method::Patch;
use crate::api::opt::PatchOp;
use crate::api::opt::Range;
use crate::api::opt::Resource;
use crate::api::Connection;
use crate::api::Result;
use crate::method::OnceLockExt;
use crate::sql::to_value;
use crate::sql::Id;
use crate::sql::Value;
use crate::opt::KeyRange;
use crate::Surreal;
use crate::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::Cow;
use std::future::IntoFuture;
use std::marker::PhantomData;
use surrealdb_core::sql::{to_value as to_core_value, Value as CoreValue};
/// An upsert future
#[derive(Debug)]
@ -25,7 +24,6 @@ use std::marker::PhantomData;
pub struct Upsert<'r, C: Connection, R> {
pub(super) client: Cow<'r, Surreal<C>>,
pub(super) resource: Result<Resource>,
pub(super) range: Option<Range<Id>>,
pub(super) response_type: PhantomData<R>,
}
@ -48,18 +46,13 @@ macro_rules! into_future {
let Upsert {
client,
resource,
range,
..
} = self;
Box::pin(async move {
let param: Value = match range {
Some(range) => resource?.with_range(range)?.into(),
None => resource?.into(),
};
let router = client.router.extract()?;
router
.$method(Command::Upsert {
what: param,
what: resource?,
data: None,
})
.await
@ -105,8 +98,8 @@ where
C: Connection,
{
/// Restricts the records to upsert to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -116,8 +109,8 @@ where
C: Connection,
{
/// Restricts the records to upsert to those in the specified range
pub fn range(mut self, bounds: impl Into<Range<Id>>) -> Self {
self.range = Some(bounds.into());
pub fn range(mut self, range: impl Into<KeyRange>) -> Self {
self.resource = self.resource.and_then(|x| x.with_range(range.into()));
self
}
}
@ -133,20 +126,15 @@ where
D: Serialize + 'static,
{
Content::from_closure(self.client, || {
let data = to_value(data)?;
let what: Value = match self.range {
Some(range) => self.resource?.with_range(range)?.into(),
None => self.resource?.into(),
};
let data = to_core_value(data)?;
let data = match data {
Value::None | Value::Null => None,
CoreValue::None => None,
content => Some(content),
};
Ok(Command::Upsert {
what,
what: self.resource?,
data,
})
})
@ -160,7 +148,6 @@ where
Merge {
client: self.client,
resource: self.resource,
range: self.range,
content: data,
response_type: PhantomData,
}
@ -171,7 +158,6 @@ where
Patch {
client: self.client,
resource: self.resource,
range: self.range,
patches: vec![patch],
response_type: PhantomData,
}

View file

@ -37,9 +37,12 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let router = self.client.router.extract()?;
let version = router.execute_value(Command::Version).await?.convert_to_string()?;
let version = router.execute_value(Command::Version).await?;
let version = version.into_inner().to_raw_string();
let semantic = version.trim_start_matches("surrealdb-");
semantic.parse().map_err(|_| Error::InvalidSemanticVersion(semantic.to_string()).into())
semantic
.parse()
.map_err(|_| Error::InvalidSemanticVersion(format!("\"{version}\"")).into())
})
}
}

View file

@ -1,23 +1,8 @@
//! Functionality for connecting to local and remote databases
pub mod engine;
pub mod err;
#[cfg(feature = "protocol-http")]
pub mod headers;
pub mod method;
pub mod opt;
mod conn;
pub use method::query::Response;
use method::BoxFuture;
use semver::Version;
use tokio::sync::watch;
use crate::api::conn::Router;
use crate::api::err::Error;
use crate::api::opt::Endpoint;
use semver::BuildMetadata;
use semver::Version;
use semver::VersionReq;
use std::fmt;
use std::fmt::Debug;
@ -25,10 +10,120 @@ use std::future::IntoFuture;
use std::marker::PhantomData;
use std::sync::Arc;
use std::sync::OnceLock;
use tokio::sync::watch;
macro_rules! transparent_wrapper{
(
$(#[$m:meta])*
$vis:vis struct $name:ident($field_vis:vis $inner:ty)
) => {
$(#[$m])*
#[repr(transparent)]
$vis struct $name($field_vis $inner);
impl $name{
#[doc(hidden)]
#[allow(dead_code)]
pub fn from_inner(inner: $inner) -> Self{
$name(inner)
}
#[doc(hidden)]
#[allow(dead_code)]
pub fn from_inner_ref(inner: &$inner) -> &Self{
unsafe{
std::mem::transmute::<&$inner,&$name>(inner)
}
}
#[doc(hidden)]
#[allow(dead_code)]
pub fn from_inner_mut(inner: &mut $inner) -> &mut Self{
unsafe{
std::mem::transmute::<&mut $inner,&mut $name>(inner)
}
}
#[doc(hidden)]
#[allow(dead_code)]
pub fn into_inner(self) -> $inner{
self.0
}
}
impl std::fmt::Display for $name{
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result{
self.0.fmt(fmt)
}
}
impl std::fmt::Debug for $name{
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result{
self.0.fmt(fmt)
}
}
};
}
macro_rules! impl_serialize_wrapper {
($ty:ty) => {
impl ::revision::Revisioned for $ty {
fn revision() -> u16 {
CoreValue::revision()
}
fn serialize_revisioned<W: std::io::Write>(
&self,
w: &mut W,
) -> Result<(), revision::Error> {
self.0.serialize_revisioned(w)
}
fn deserialize_revisioned<R: std::io::Read>(r: &mut R) -> Result<Self, revision::Error>
where
Self: Sized,
{
::revision::Revisioned::deserialize_revisioned(r).map(Self::from_inner)
}
}
impl ::serde::Serialize for $ty {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::ser::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> ::serde::de::Deserialize<'de> for $ty {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
Ok(Self::from_inner(::serde::de::Deserialize::deserialize(deserializer)?))
}
}
};
}
pub mod engine;
pub mod err;
#[cfg(feature = "protocol-http")]
pub mod headers;
pub mod method;
pub mod opt;
pub mod value;
mod conn;
use self::conn::Router;
use self::err::Error;
use self::opt::Endpoint;
use self::opt::EndpointKind;
use self::opt::WaitFor;
pub use method::query::Response;
/// A specialized `Result` type
pub type Result<T> = std::result::Result<T, crate::Error>;

View file

@ -1,5 +1,4 @@
//! The different options and types for use in API functions
use crate::sql::Thing;
use dmp::Diff;
use serde::Serialize;
@ -23,9 +22,6 @@ use serde_content::Value as Content;
#[cfg(any(feature = "native-tls", feature = "rustls"))]
pub use tls::*;
/// Record ID
pub type RecordId = Thing;
type UnitOp<'a> = InnerOp<'a, ()>;
#[derive(Debug, Serialize)]

View file

@ -1,14 +1,21 @@
use crate::api::{err::Error, Response as QueryResponse, Result};
use crate::method;
use crate::method::{Stats, Stream};
use crate::sql::from_value;
use crate::sql::{self, statements::*, Statement, Statements, Value};
use crate::{syn, Notification};
use crate::{
api::{err::Error, Response as QueryResponse, Result},
method::{self, Stats, Stream},
value::Notification,
Value,
};
use futures::future::Either;
use futures::stream::select_all;
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use std::mem;
use surrealdb_core::{
sql::{
self, from_value as from_core_value, statements::*, Statement, Statements,
Value as CoreValue,
},
syn,
};
/// A trait for converting inputs into SQL statements
pub trait IntoQuery {
@ -195,8 +202,8 @@ where
impl QueryResult<Value> for usize {
fn query_result(self, response: &mut QueryResponse) -> Result<Value> {
match response.results.swap_remove(&self) {
Some((_, result)) => Ok(result?),
None => Ok(Value::None),
Some((_, result)) => Ok(Value::from_inner(result?)),
None => Ok(Value::from_inner(CoreValue::None)),
}
}
@ -224,11 +231,11 @@ where
}
};
let result = match value {
Value::Array(vec) => match &mut vec.0[..] {
CoreValue::Array(vec) => match &mut vec.0[..] {
[] => Ok(None),
[value] => {
let value = mem::take(value);
from_value(value).map_err(Into::into)
from_core_value(value).map_err(Into::into)
}
_ => Err(Error::LossyTake(QueryResponse {
results: mem::take(&mut response.results),
@ -239,7 +246,7 @@ where
},
_ => {
let value = mem::take(value);
from_value(value).map_err(Into::into)
from_core_value(value).map_err(Into::into)
}
};
response.results.swap_remove(&self);
@ -264,16 +271,16 @@ impl QueryResult<Value> for (usize, &str) {
}
},
None => {
return Ok(Value::None);
return Ok(Value::from_inner(CoreValue::None));
}
};
let value = match value {
Value::Object(object) => object.remove(key).unwrap_or_default(),
_ => Value::None,
CoreValue::Object(object) => object.remove(key).unwrap_or_default(),
_ => CoreValue::None,
};
Ok(value)
Ok(Value::from_inner(value))
}
fn stats(&self, response: &QueryResponse) -> Option<Stats> {
@ -301,7 +308,7 @@ where
}
};
let value = match value {
Value::Array(vec) => match &mut vec.0[..] {
CoreValue::Array(vec) => match &mut vec.0[..] {
[] => {
response.results.swap_remove(&index);
return Ok(None);
@ -319,11 +326,11 @@ where
value => value,
};
match value {
Value::None | Value::Null => {
CoreValue::None => {
response.results.swap_remove(&index);
Ok(None)
}
Value::Object(object) => {
CoreValue::Object(object) => {
if object.is_empty() {
response.results.swap_remove(&index);
return Ok(None);
@ -331,7 +338,7 @@ where
let Some(value) = object.remove(key) else {
return Ok(None);
};
from_value(value).map_err(Into::into)
from_core_value(value).map_err(Into::into)
}
_ => Ok(None),
}
@ -349,14 +356,14 @@ where
fn query_result(self, response: &mut QueryResponse) -> Result<Vec<T>> {
let vec = match response.results.swap_remove(&self) {
Some((_, result)) => match result? {
Value::Array(vec) => vec.0,
CoreValue::Array(vec) => vec.0,
vec => vec![vec],
},
None => {
return Ok(vec![]);
}
};
from_value(vec.into()).map_err(Into::into)
from_core_value(vec.into()).map_err(Into::into)
}
fn stats(&self, response: &QueryResponse) -> Option<Stats> {
@ -373,7 +380,7 @@ where
let mut response = match response.results.get_mut(&index) {
Some((_, result)) => match result {
Ok(val) => match val {
Value::Array(vec) => mem::take(&mut vec.0),
CoreValue::Array(vec) => mem::take(&mut vec.0),
val => {
let val = mem::take(val);
vec![val]
@ -391,13 +398,13 @@ where
};
let mut vec = Vec::with_capacity(response.len());
for value in response.iter_mut() {
if let Value::Object(object) = value {
if let CoreValue::Object(object) = value {
if let Some(value) = object.remove(key) {
vec.push(value);
}
}
}
from_value(vec.into()).map_err(Into::into)
from_core_value(vec.into()).map_err(Into::into)
}
fn stats(&self, response: &QueryResponse) -> Option<Stats> {

View file

@ -1,58 +1,115 @@
use crate::api::{err::Error, Result};
use crate::sql::{self, Array, Edges, Id, Object, Table, Thing, Value};
use crate::syn;
use crate::{
api::{err::Error, Result},
Object, RecordId, RecordIdKey, Value,
};
use std::ops::{self, Bound};
use surrealdb_core::sql::{
Edges as CoreEdges, IdRange as CoreIdRange, Table as CoreTable, Thing as CoreThing,
};
#[cfg(any(feature = "protocol-ws", feature = "protocol-http"))]
use surrealdb_core::sql::Value as CoreValue;
/// A wrapper type to assert that you ment to use a string as a table name.
///
/// To prevent some possible errors, by defauit [`IntoResource`] does not allow `:` in table names
/// as this might be an indication that the user might have intended to use a record id instead.
/// If you wrap your table name string in this tupe the [`IntoResource`] trait will accept any
/// table names.
#[derive(Debug)]
pub struct Table<T>(pub T);
impl<T> Table<T>
where
T: Into<String>,
{
pub(crate) fn into_core(self) -> CoreTable {
let mut t = CoreTable::default();
t.0 = self.0.into();
t
}
/// Add a range of keys to the table.
pub fn with_range<R>(self, range: R) -> QueryRange
where
KeyRange: From<R>,
{
let range = KeyRange::from(range);
let res = CoreIdRange {
beg: range.start.map(RecordIdKey::into_inner),
end: range.end.map(RecordIdKey::into_inner),
};
let res = CoreThing::from((self.0.into(), res));
QueryRange(res)
}
}
transparent_wrapper!(
/// A table range.
#[derive( Clone, PartialEq)]
pub struct QueryRange(CoreThing)
);
transparent_wrapper!(
/// A query edge
#[derive( Clone, PartialEq)]
pub struct Edge(CoreEdges)
);
/// A database resource
#[derive(Debug)]
///
/// A resource is a location, or a range of locations, from which data can be fetched.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Resource {
/// Table name
Table(Table),
Table(String),
/// Record ID
RecordId(Thing),
RecordId(RecordId),
/// An object
Object(Object),
/// An array
Array(Array),
Array(Vec<Value>),
/// Edges
Edges(Edges),
Edge(Edge),
/// A range of id's on a table.
Range(QueryRange),
}
impl Resource {
pub(crate) fn with_range(self, range: Range<Id>) -> Result<sql::Thing> {
/// Add a range to the resource, this only works if the resource is a table.
pub fn with_range(self, range: KeyRange) -> Result<Self> {
match self {
Resource::Table(table) => Ok(sql::Thing::from((
table.0,
sql::Id::Range(Box::new(sql::IdRange::try_from((range.start, range.end))?)),
))),
Resource::RecordId(record_id) => Err(Error::RangeOnRecordId(record_id).into()),
Resource::Object(object) => Err(Error::RangeOnObject(object).into()),
Resource::Array(array) => Err(Error::RangeOnArray(array).into()),
Resource::Edges(edges) => Err(Error::RangeOnEdges(edges).into()),
Resource::Table(table) => Ok(Resource::Range(Table(table).with_range(range))),
Resource::RecordId(_) => Err(Error::RangeOnRecordId.into()),
Resource::Object(_) => Err(Error::RangeOnObject.into()),
Resource::Array(_) => Err(Error::RangeOnArray.into()),
Resource::Edge(_) => Err(Error::RangeOnEdges.into()),
Resource::Range(_) => Err(Error::RangeOnRange.into()),
}
}
#[cfg(any(feature = "protocol-ws", feature = "protocol-http"))]
pub(crate) fn into_core_value(self) -> CoreValue {
match self {
Resource::Table(x) => Table(x).into_core().into(),
Resource::RecordId(x) => x.into_inner().into(),
Resource::Object(x) => x.into_inner().into(),
Resource::Array(x) => Value::array_to_core(x).into(),
Resource::Edge(x) => x.into_inner().into(),
Resource::Range(x) => x.into_inner().into(),
}
}
}
impl From<Table> for Resource {
fn from(table: Table) -> Self {
Self::Table(table)
}
}
impl From<&Table> for Resource {
fn from(table: &Table) -> Self {
Self::Table(table.clone())
}
}
impl From<Thing> for Resource {
fn from(thing: Thing) -> Self {
impl From<RecordId> for Resource {
fn from(thing: RecordId) -> Self {
Self::RecordId(thing)
}
}
impl From<&Thing> for Resource {
fn from(thing: &Thing) -> Self {
impl From<&RecordId> for Resource {
fn from(thing: &RecordId) -> Self {
Self::RecordId(thing.clone())
}
}
@ -69,36 +126,21 @@ impl From<&Object> for Resource {
}
}
impl From<Array> for Resource {
fn from(array: Array) -> Self {
impl From<Vec<Value>> for Resource {
fn from(array: Vec<Value>) -> Self {
Self::Array(array)
}
}
impl From<&Array> for Resource {
fn from(array: &Array) -> Self {
Self::Array(array.clone())
}
}
impl From<Edges> for Resource {
fn from(edges: Edges) -> Self {
Self::Edges(edges)
}
}
impl From<&Edges> for Resource {
fn from(edges: &Edges) -> Self {
Self::Edges(edges.clone())
impl From<&[Value]> for Resource {
fn from(array: &[Value]) -> Self {
Self::Array(array.to_vec())
}
}
impl From<&str> for Resource {
fn from(s: &str) -> Self {
match syn::thing(s) {
Ok(thing) => Self::RecordId(thing),
Err(_) => Self::Table(s.into()),
}
Resource::from(s.to_string())
}
}
@ -110,146 +152,43 @@ impl From<&String> for Resource {
impl From<String> for Resource {
fn from(s: String) -> Self {
match syn::thing(s.as_str()) {
Ok(thing) => Self::RecordId(thing),
Err(_) => Self::Table(s.into()),
}
Resource::Table(s)
}
}
impl From<Edge> for Resource {
fn from(value: Edge) -> Self {
Resource::Edge(value)
}
}
impl From<QueryRange> for Resource {
fn from(value: QueryRange) -> Self {
Resource::Range(value)
}
}
impl<T, I> From<(T, I)> for Resource
where
T: Into<String>,
I: Into<Id>,
I: Into<RecordIdKey>,
{
fn from((table, id): (T, I)) -> Self {
let record_id = (table.into(), id.into());
Self::RecordId(record_id.into())
}
}
impl From<Resource> for Value {
fn from(resource: Resource) -> Self {
match resource {
Resource::Table(resource) => resource.into(),
Resource::RecordId(resource) => resource.into(),
Resource::Object(resource) => resource.into(),
Resource::Array(resource) => resource.into(),
Resource::Edges(resource) => resource.into(),
}
}
}
/// A trait for converting inputs into database resources
pub trait IntoResource<Response>: Sized {
/// Converts an input into a database resource
fn into_resource(self) -> Result<Resource>;
}
impl IntoResource<Value> for Resource {
fn into_resource(self) -> Result<Resource> {
Ok(self)
}
}
impl<R> IntoResource<Option<R>> for Object {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::Object(self))
}
}
impl<R> IntoResource<Option<R>> for Thing {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::RecordId(self))
}
}
impl<R> IntoResource<Option<R>> for &Thing {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::RecordId(self.clone()))
}
}
impl<R, T, I> IntoResource<Option<R>> for (T, I)
where
T: Into<String>,
I: Into<Id>,
{
fn into_resource(self) -> Result<Resource> {
let (table, id) = self;
let record_id = (table.into(), id.into());
Ok(Resource::RecordId(record_id.into()))
}
}
impl<R> IntoResource<Vec<R>> for Array {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::Array(self))
}
}
impl<R> IntoResource<Vec<R>> for Edges {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::Edges(self))
}
}
impl<R> IntoResource<Vec<R>> for Table {
fn into_resource(self) -> Result<Resource> {
Ok(Resource::Table(self))
}
}
fn blacklist_colon(input: &str) -> Result<()> {
match input.contains(':') {
true => {
// We already know this string contains a colon
let (table, id) = input.split_once(':').unwrap();
Err(Error::TableColonId {
table: table.to_owned(),
id: id.to_owned(),
}
.into())
}
false => Ok(()),
}
}
impl<R> IntoResource<Vec<R>> for &str {
fn into_resource(self) -> Result<Resource> {
blacklist_colon(self)?;
let mut table = Table::default();
self.clone_into(&mut table.0);
Ok(Resource::Table(table))
}
}
impl<R> IntoResource<Vec<R>> for &String {
fn into_resource(self) -> Result<Resource> {
blacklist_colon(self)?;
IntoResource::<Vec<R>>::into_resource(self.as_str())
}
}
impl<R> IntoResource<Vec<R>> for String {
fn into_resource(self) -> Result<Resource> {
blacklist_colon(&self)?;
let mut table = Table::default();
table.0 = self;
Ok(Resource::Table(table))
let record_id = RecordId::from_table_key(table, id);
Self::RecordId(record_id)
}
}
/// Holds the `start` and `end` bounds of a range query
#[derive(Debug)]
pub struct Range<T> {
pub(crate) start: Bound<T>,
pub(crate) end: Bound<T>,
#[derive(Debug, PartialEq, Clone)]
pub struct KeyRange {
pub(crate) start: Bound<RecordIdKey>,
pub(crate) end: Bound<RecordIdKey>,
}
impl<T> From<(Bound<T>, Bound<T>)> for Range<Id>
impl<T> From<(Bound<T>, Bound<T>)> for KeyRange
where
T: Into<Id>,
T: Into<RecordIdKey>,
{
fn from((start, end): (Bound<T>, Bound<T>)) -> Self {
Self {
@ -267,9 +206,9 @@ where
}
}
impl<T> From<ops::Range<T>> for Range<Id>
impl<T> From<ops::Range<T>> for KeyRange
where
T: Into<Id>,
T: Into<RecordIdKey>,
{
fn from(
ops::Range {
@ -284,9 +223,9 @@ where
}
}
impl<T> From<ops::RangeInclusive<T>> for Range<Id>
impl<T> From<ops::RangeInclusive<T>> for KeyRange
where
T: Into<Id>,
T: Into<RecordIdKey>,
{
fn from(range: ops::RangeInclusive<T>) -> Self {
let (start, end) = range.into_inner();
@ -297,9 +236,9 @@ where
}
}
impl<T> From<ops::RangeFrom<T>> for Range<Id>
impl<T> From<ops::RangeFrom<T>> for KeyRange
where
T: Into<Id>,
T: Into<RecordIdKey>,
{
fn from(
ops::RangeFrom {
@ -313,9 +252,9 @@ where
}
}
impl<T> From<ops::RangeTo<T>> for Range<Id>
impl<T> From<ops::RangeTo<T>> for KeyRange
where
T: Into<Id>,
T: Into<RecordIdKey>,
{
fn from(
ops::RangeTo {
@ -329,9 +268,9 @@ where
}
}
impl<T> From<ops::RangeToInclusive<T>> for Range<Id>
impl<T> From<ops::RangeToInclusive<T>> for KeyRange
where
T: Into<Id>,
T: Into<RecordIdKey>,
{
fn from(
ops::RangeToInclusive {
@ -345,7 +284,7 @@ where
}
}
impl From<ops::RangeFull> for Range<Id> {
impl From<ops::RangeFull> for KeyRange {
fn from(_: ops::RangeFull) -> Self {
Self {
start: Bound::Unbounded,
@ -353,3 +292,101 @@ impl From<ops::RangeFull> for Range<Id> {
}
}
}
/// A trait for types which can be used as a resource selection for a query.
pub trait IntoResource<Output> {
fn into_resource(self) -> Result<Resource>;
}
fn no_colon(a: &str) -> Result<()> {
if a.contains(':') {
return Err(Error::TableColonId {
table: a.to_string(),
}
.into());
}
Ok(())
}
impl IntoResource<Value> for Resource {
fn into_resource(self) -> Result<Resource> {
Ok(self)
}
}
impl<R> IntoResource<Option<R>> for Object {
fn into_resource(self) -> Result<Resource> {
Ok(self.into())
}
}
impl<R> IntoResource<Option<R>> for RecordId {
fn into_resource(self) -> Result<Resource> {
Ok(self.into())
}
}
impl<R> IntoResource<Option<R>> for &RecordId {
fn into_resource(self) -> Result<Resource> {
Ok(self.clone().into())
}
}
impl<R, T, I> IntoResource<Option<R>> for (T, I)
where
T: Into<String>,
I: Into<RecordIdKey>,
{
fn into_resource(self) -> Result<Resource> {
Ok(self.into())
}
}
impl<R> IntoResource<Vec<R>> for Vec<Value> {
fn into_resource(self) -> Result<Resource> {
Ok(self.into())
}
}
impl<R> IntoResource<Vec<R>> for Edge {
fn into_resource(self) -> Result<Resource> {
Ok(self.into())
}
}
impl<R> IntoResource<Vec<R>> for QueryRange {
fn into_resource(self) -> Result<Resource> {
Ok(self.into())
}
}
impl<T, R> IntoResource<Vec<R>> for Table<T>
where
T: Into<String>,
{
fn into_resource(self) -> Result<Resource> {
let t = self.0.into();
Ok(t.into())
}
}
impl<R> IntoResource<Vec<R>> for &str {
fn into_resource(self) -> Result<Resource> {
no_colon(self)?;
Ok(self.into())
}
}
impl<R> IntoResource<Vec<R>> for String {
fn into_resource(self) -> Result<Resource> {
no_colon(&self)?;
Ok(self.into())
}
}
impl<R> IntoResource<Vec<R>> for &String {
fn into_resource(self) -> Result<Resource> {
no_colon(self)?;
Ok(self.into())
}
}

421
lib/src/api/value/core.rs Normal file
View file

@ -0,0 +1,421 @@
use crate::{
opt::{Edge, KeyRange, QueryRange},
Bytes, Datetime, Number, Object, RecordId, RecordIdKey, Uuid, Value,
};
use std::{collections::BTreeMap, ops::Bound};
use surrealdb_core::{
dbs::{Action as CoreAction, Notification as CoreNotification},
sql::{
Array as CoreArray, Bytes as CoreBytes, Datetime as CoreDatetime, Duration as CoreDuration,
Edges as CoreEdges, Id, Number as CoreNumber, Object as CoreObject, Range as CoreRange,
Strand, Table as CoreTable, Tables as CoreTables, Thing, Uuid as CoreUuid,
Value as CoreValue,
},
};
use trice::Duration;
use super::{Action, Notification};
pub(crate) trait ToCore: Sized {
type Core;
fn to_core(self) -> Self::Core;
fn as_core(&self) -> Self::Core;
/// Create a lib value from it's core counterpart.
/// Can return none if the core value cannot be turned into the core value.
/// This can be the case with forexample value, where it's AST like values are not present on
/// the lib value.
fn from_core(this: Self::Core) -> Option<Self>;
}
impl ToCore for Bytes {
type Core = CoreBytes;
fn to_core(self) -> Self::Core {
CoreBytes::from(self.0)
}
fn as_core(&self) -> Self::Core {
self.clone().to_core()
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(Self(this.into_inner()))
}
}
impl ToCore for Datetime {
type Core = CoreDatetime;
fn to_core(self) -> Self::Core {
CoreDatetime::from(self.0)
}
fn as_core(&self) -> Self::Core {
self.clone().to_core()
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(Self(this.0))
}
}
impl ToCore for Object {
type Core = CoreObject;
fn to_core(self) -> Self::Core {
CoreObject::from(
self.0
.into_iter()
.map(|(k, v)| (k, v.to_core()))
.collect::<BTreeMap<String, CoreValue>>(),
)
}
fn as_core(&self) -> Self::Core {
let map: BTreeMap<String, CoreValue> =
self.0.iter().map(|(k, v)| (k.clone(), v.as_core())).collect();
CoreObject::from(map)
}
fn from_core(this: Self::Core) -> Option<Self> {
let mut new = BTreeMap::new();
for (k, v) in this.0.into_iter() {
new.insert(k, ToCore::from_core(v)?);
}
Some(Object(new))
}
}
impl ToCore for Vec<Value> {
type Core = CoreArray;
fn to_core(self) -> Self::Core {
CoreArray::from(self.into_iter().map(ToCore::to_core).collect::<Vec<CoreValue>>())
}
fn as_core(&self) -> Self::Core {
CoreArray::from(self.iter().map(ToCore::as_core).collect::<Vec<CoreValue>>())
}
fn from_core(this: Self::Core) -> Option<Self> {
let len = this.0.len();
this.0.into_iter().try_fold(Vec::<Value>::with_capacity(len), |mut acc, r| {
acc.push(Value::from_core(r)?);
Some(acc)
})
}
}
impl ToCore for Number {
type Core = CoreNumber;
fn to_core(self) -> Self::Core {
match self {
Number::Float(x) => CoreNumber::Float(x),
Number::Integer(x) => CoreNumber::Int(x),
Number::Decimal(x) => CoreNumber::Decimal(x),
}
}
fn as_core(&self) -> Self::Core {
self.clone().to_core()
}
fn from_core(this: Self::Core) -> Option<Self> {
let v = match this {
CoreNumber::Int(x) => Number::Integer(x),
CoreNumber::Float(x) => Number::Float(x),
CoreNumber::Decimal(x) => Number::Decimal(x),
_ => return None,
};
Some(v)
}
}
impl ToCore for Uuid {
type Core = CoreUuid;
fn to_core(self) -> Self::Core {
CoreUuid::from(self)
}
fn as_core(&self) -> Self::Core {
self.to_core()
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(this.0)
}
}
impl ToCore for String {
type Core = Strand;
fn to_core(self) -> Self::Core {
Strand::from(self)
}
fn as_core(&self) -> Self::Core {
Strand::from(self.as_str())
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(this.0)
}
}
impl ToCore for Duration {
type Core = CoreDuration;
fn to_core(self) -> Self::Core {
CoreDuration::from(self)
}
fn as_core(&self) -> Self::Core {
CoreDuration::from(*self)
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(this.0)
}
}
impl ToCore for RecordId {
type Core = Thing;
fn to_core(self) -> Self::Core {
Thing::from((self.table, self.key.to_core()))
}
fn as_core(&self) -> Self::Core {
Thing::from((self.table.clone(), self.key.as_core()))
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(Self {
table: this.tb,
key: ToCore::from_core(this.id)?,
})
}
}
impl ToCore for RecordIdKey {
type Core = Id;
fn to_core(self) -> Self::Core {
match self {
RecordIdKey::String(x) => Id::String(x),
RecordIdKey::Integer(x) => Id::Number(x),
RecordIdKey::Object(x) => Id::Object(x.to_core()),
RecordIdKey::Array(x) => Id::Array(x.to_core()),
}
}
fn as_core(&self) -> Self::Core {
match self {
RecordIdKey::String(x) => Id::String(x.clone()),
RecordIdKey::Integer(x) => Id::Number(*x),
RecordIdKey::Object(x) => Id::Object(x.as_core()),
RecordIdKey::Array(x) => Id::Array(x.as_core()),
}
}
fn from_core(this: Self::Core) -> Option<Self> {
let v = match this {
Id::String(x) => RecordIdKey::String(x),
Id::Number(x) => RecordIdKey::Integer(x),
Id::Object(x) => RecordIdKey::Object(ToCore::from_core(x)?),
Id::Array(x) => RecordIdKey::Array(ToCore::from_core(x)?),
_ => return None,
};
Some(v)
}
}
impl ToCore for Edge {
type Core = CoreEdges;
fn to_core(self) -> Self::Core {
let from = self.from.to_core();
let mut what = CoreTables::default();
what.0 = self
.tables
.into_iter()
.map(|x| {
let mut t = CoreTable::default();
t.0 = x;
t
})
.collect();
CoreEdges::new(self.dir, from, what)
}
fn as_core(&self) -> Self::Core {
let from = self.from.as_core();
let mut what = CoreTables::default();
what.0 = self
.tables
.iter()
.map(|x| {
let mut t = CoreTable::default();
t.0 = x.clone();
t
})
.collect();
CoreEdges::new(self.dir.clone(), from, what)
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(Edge {
dir: this.dir,
from: RecordId::from_core(this.from)?,
tables: this.what.0.into_iter().map(|x| x.0).collect(),
})
}
}
impl ToCore for Notification<Value> {
type Core = CoreNotification;
fn to_core(self) -> Self::Core {
CoreNotification::new(self.query_id.into(), self.action.to_core(), self.data.to_core())
}
fn as_core(&self) -> Self::Core {
CoreNotification::new(self.query_id.into(), self.action.as_core(), self.data.as_core())
}
fn from_core(this: Self::Core) -> Option<Self> {
Some(Notification {
query_id: this.id.0,
action: Action::from_core(this.action)?,
data: Value::from_core(this.result)?,
})
}
}
impl ToCore for QueryRange {
type Core = CoreRange;
fn to_core(self) -> Self::Core {
CoreRange::new(
self.table,
self.range.start.map(|x| x.to_core()),
self.range.end.map(|x| x.to_core()),
)
}
fn as_core(&self) -> Self::Core {
self.clone().to_core()
}
fn from_core(this: Self::Core) -> Option<Self> {
let start = match this.beg {
Bound::Included(x) => Bound::Included(RecordIdKey::from_core(x)?),
Bound::Excluded(x) => Bound::Excluded(RecordIdKey::from_core(x)?),
Bound::Unbounded => Bound::Unbounded,
};
let end = match this.end {
Bound::Included(x) => Bound::Included(RecordIdKey::from_core(x)?),
Bound::Excluded(x) => Bound::Excluded(RecordIdKey::from_core(x)?),
Bound::Unbounded => Bound::Unbounded,
};
Some(QueryRange {
table: this.tb,
range: KeyRange {
start,
end,
},
})
}
}
impl ToCore for Action {
type Core = CoreAction;
fn to_core(self) -> Self::Core {
match self {
Action::Create => CoreAction::Create,
Action::Update => CoreAction::Update,
Action::Delete => CoreAction::Delete,
}
}
fn as_core(&self) -> Self::Core {
match self {
Action::Create => CoreAction::Create,
Action::Update => CoreAction::Update,
Action::Delete => CoreAction::Delete,
}
}
fn from_core(this: Self::Core) -> Option<Self> {
match this {
CoreAction::Create => Some(Action::Create),
CoreAction::Update => Some(Action::Update),
CoreAction::Delete => Some(Action::Delete),
_ => None,
}
}
}
impl ToCore for Value {
type Core = CoreValue;
fn to_core(self) -> Self::Core {
match self {
Value::None => CoreValue::None,
Value::Bool(x) => CoreValue::Bool(x),
Value::Number(x) => CoreValue::Number(x.to_core()),
Value::Object(x) => CoreValue::Object(x.to_core()),
Value::String(x) => CoreValue::Strand(Strand::from(x)),
Value::Array(x) => CoreValue::Array(x.to_core()),
Value::Uuid(x) => CoreValue::Uuid(x.to_core()),
Value::Datetime(x) => CoreValue::Datetime(x.to_core()),
Value::Duration(x) => CoreValue::Duration(x.to_core()),
Value::Bytes(x) => CoreValue::Bytes(x.to_core()),
Value::RecordId(x) => CoreValue::Thing(x.to_core()),
}
}
fn as_core(&self) -> Self::Core {
match self {
Value::None => CoreValue::None,
Value::Bool(x) => CoreValue::Bool(*x),
Value::Number(x) => CoreValue::Number(x.as_core()),
Value::Object(x) => CoreValue::Object(x.as_core()),
Value::String(x) => CoreValue::Strand(Strand::from(x.as_str())),
Value::Array(x) => CoreValue::Array(x.as_core()),
Value::Uuid(x) => CoreValue::Uuid(x.as_core()),
Value::Datetime(x) => CoreValue::Datetime(x.as_core()),
Value::Duration(x) => CoreValue::Duration(x.as_core()),
Value::Bytes(x) => CoreValue::Bytes(x.as_core()),
Value::RecordId(x) => CoreValue::Thing(x.as_core()),
}
}
fn from_core(this: Self::Core) -> Option<Self> {
let v = match this {
CoreValue::None | CoreValue::Null => Value::None,
CoreValue::Bool(x) => Value::Bool(x),
CoreValue::Number(x) => Value::Number(ToCore::from_core(x)?),
CoreValue::Object(x) => Value::Object(ToCore::from_core(x)?),
CoreValue::Strand(x) => Value::String(ToCore::from_core(x)?),
CoreValue::Array(x) => Value::Array(ToCore::from_core(x)?),
CoreValue::Uuid(x) => Value::Uuid(ToCore::from_core(x)?),
CoreValue::Datetime(x) => Value::Datetime(ToCore::from_core(x)?),
CoreValue::Duration(x) => Value::Duration(ToCore::from_core(x)?),
CoreValue::Bytes(x) => Value::Bytes(ToCore::from_core(x)?),
CoreValue::Thing(x) => Value::RecordId(ToCore::from_core(x)?),
_ => return None,
};
Some(v)
}
}

371
lib/src/api/value/mod.rs Normal file
View file

@ -0,0 +1,371 @@
use crate::Error;
use revision::revisioned;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
cmp::{Ordering, PartialEq, PartialOrd},
fmt,
ops::Deref,
str::FromStr,
};
use surrealdb_core::{
dbs::Action as CoreAction,
sql::{
Array as CoreArray, Datetime as CoreDatetime, Id as CoreId, Number as CoreNumber,
Thing as CoreThing, Value as CoreValue,
},
syn,
};
use uuid::Uuid;
mod obj;
pub use obj::{IntoIter, Iter, IterMut, Object};
pub fn from_value<T: DeserializeOwned>(value: Value) -> Result<T, Error> {
Ok(surrealdb_core::sql::from_value(value.0)?)
}
pub fn to_value<T: Serialize + 'static>(value: T) -> Result<Value, Error> {
let v = surrealdb_core::sql::to_value(value)?;
Ok(Value(v))
}
// Keeping bytes implementation minimal since it might be a good idea to use bytes crate here
// instead of a plain Vec<u8>.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[revisioned(revision = 1)]
pub struct Bytes(Vec<u8>);
impl Bytes {
pub fn copy_from_slice(slice: &[u8]) -> Self {
slice.to_vec().into()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl PartialEq<[u8]> for Bytes {
fn eq(&self, other: &[u8]) -> bool {
self.0 == other
}
}
impl PartialOrd<[u8]> for Bytes {
fn partial_cmp(&self, other: &[u8]) -> Option<Ordering> {
self.0.as_slice().partial_cmp(other)
}
}
impl Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.0.as_slice()
}
}
impl From<Vec<u8>> for Bytes {
fn from(value: Vec<u8>) -> Self {
Bytes(value)
}
}
transparent_wrapper!(
#[derive( Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Datetime(CoreDatetime)
);
transparent_wrapper!(
/// The key of a [`RecordId`].
#[derive( Clone, PartialEq, PartialOrd)]
#[non_exhaustive]
pub struct RecordIdKey(CoreId)
);
impl From<Object> for RecordIdKey {
fn from(value: Object) -> Self {
Self::from_inner(CoreId::Object(value.into_inner()))
}
}
impl From<String> for RecordIdKey {
fn from(value: String) -> Self {
Self(CoreId::String(value))
}
}
impl From<&String> for RecordIdKey {
fn from(value: &String) -> Self {
Self(CoreId::String(value.clone()))
}
}
impl From<&str> for RecordIdKey {
fn from(value: &str) -> Self {
Self(CoreId::String(value.to_owned()))
}
}
impl From<i64> for RecordIdKey {
fn from(value: i64) -> Self {
Self(CoreId::Number(value))
}
}
impl From<Vec<Value>> for RecordIdKey {
fn from(value: Vec<Value>) -> Self {
let res = Value::array_to_core(value);
let mut array = CoreArray::default();
array.0 = res;
Self(CoreId::Array(array))
}
}
impl From<RecordIdKey> for Value {
fn from(key: RecordIdKey) -> Self {
match key.0 {
CoreId::String(x) => Value::from_inner(CoreValue::from(x)),
CoreId::Number(x) => Value::from_inner(CoreValue::from(x)),
CoreId::Object(x) => Value::from_inner(CoreValue::from(x)),
CoreId::Array(x) => Value::from_inner(CoreValue::from(x)),
_ => panic!("lib recieved generate variant of record id"),
}
}
}
impl From<RecordId> for Value {
fn from(key: RecordId) -> Self {
Value::from_inner(CoreValue::Thing(key.0))
}
}
impl FromStr for Value {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Value::from_inner(surrealdb_core::syn::value(s)?))
}
}
#[derive(Debug)]
pub struct RecordIdKeyFromValueError(());
impl fmt::Display for RecordIdKeyFromValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f,"tried to convert a value to a record id key with a value type that is not allowed in a record id key")
}
}
impl TryFrom<Value> for RecordIdKey {
type Error = RecordIdKeyFromValueError;
fn try_from(key: Value) -> Result<Self, Self::Error> {
match key.0 {
CoreValue::Strand(x) => Ok(RecordIdKey::from_inner(CoreId::String(x.0))),
CoreValue::Number(CoreNumber::Int(x)) => Ok(RecordIdKey::from_inner(CoreId::Number(x))),
CoreValue::Object(x) => Ok(RecordIdKey::from_inner(CoreId::Object(x))),
CoreValue::Array(x) => Ok(RecordIdKey::from_inner(CoreId::Array(x))),
_ => Err(RecordIdKeyFromValueError(())),
}
}
}
transparent_wrapper!(
/// Struct representing a record id.
///
/// Record id's consist of a table name and a key.
/// For example the record id `user:tkwse1j5o0anqjxonvzx` has the table `user` and the key `tkwse1j5o0anqjxonvzx`.
#[derive( Clone, PartialEq, PartialOrd)]
pub struct RecordId(CoreThing)
);
impl_serialize_wrapper!(RecordId);
impl RecordId {
pub fn from_table_key<S, K>(table: S, key: K) -> Self
where
S: Into<String>,
K: Into<RecordIdKey>,
{
let tb = table.into();
let key = key.into();
Self(CoreThing::from((tb, key.0)))
}
pub fn table(&self) -> &str {
&self.0.tb
}
pub fn key(&self) -> &RecordIdKey {
RecordIdKey::from_inner_ref(&self.0.id)
}
}
impl FromStr for RecordId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
syn::thing(s).map_err(crate::Error::Db).map(RecordId::from_inner)
}
}
impl<S, I> From<(S, I)> for RecordId
where
S: Into<String>,
RecordIdKey: From<I>,
{
fn from(value: (S, I)) -> Self {
Self::from_table_key(value.0, value.1)
}
}
transparent_wrapper!(
/// The number type of surrealql.
/// Can contain either a 64 bit float, 64 bit integer or a decimal.
#[derive( Clone, PartialEq, PartialOrd)]
pub struct Number(CoreNumber)
);
impl_serialize_wrapper!(Number);
impl Number {
#[doc(hidden)]
pub fn cource_into_i64(self) -> Option<i64> {
match self.0 {
CoreNumber::Int(x) => Some(x),
CoreNumber::Float(x) if x.fract() == x => Some(x as i64),
CoreNumber::Decimal(x) => x.try_into().ok(),
_ => None,
}
}
}
transparent_wrapper!(
#[derive( Clone, Default, PartialEq, PartialOrd)]
pub struct Value(pub(crate) CoreValue)
);
impl_serialize_wrapper!(Value);
impl Value {
// TODO: Check if all of theses are actually used.
#[allow(dead_code)]
pub(crate) fn core_to_array(v: Vec<CoreValue>) -> Vec<Value> {
unsafe {
// SAFETY: Because Value is `repr(transparent)` transmuting between value and corevalue
// is safe.
std::mem::transmute::<Vec<CoreValue>, Vec<Value>>(v)
}
}
#[allow(dead_code)]
pub(crate) fn core_to_array_ref(v: &Vec<CoreValue>) -> &Vec<Value> {
unsafe {
// SAFETY: Because Value is `repr(transparent)` transmuting between value and corevalue
// is safe.
std::mem::transmute::<&Vec<CoreValue>, &Vec<Value>>(v)
}
}
#[allow(dead_code)]
pub(crate) fn core_to_array_mut(v: &mut Vec<CoreValue>) -> &mut Vec<Value> {
unsafe {
// SAFETY: Because Value is `repr(transparent)` transmuting between value and corevalue
// is safe.
std::mem::transmute::<&mut Vec<CoreValue>, &mut Vec<Value>>(v)
}
}
#[allow(dead_code)]
pub(crate) fn array_to_core(v: Vec<Value>) -> Vec<CoreValue> {
unsafe {
// SAFETY: Because Value is `repr(transparent)` transmuting between value and corevalue
// is safe.
std::mem::transmute::<Vec<Value>, Vec<CoreValue>>(v)
}
}
#[allow(dead_code)]
pub(crate) fn array_to_core_ref(v: &Vec<Value>) -> &Vec<CoreValue> {
unsafe {
// SAFETY: Because Value is `repr(transparent)` transmuting between value and corevalue
// is safe.
std::mem::transmute::<&Vec<Value>, &Vec<CoreValue>>(v)
}
}
#[allow(dead_code)]
pub(crate) fn array_to_core_mut(v: &mut Vec<Value>) -> &mut Vec<CoreValue> {
unsafe {
// SAFETY: Because Value is `repr(transparent)` transmuting between value and corevalue
// is safe.
std::mem::transmute::<&mut Vec<Value>, &mut Vec<CoreValue>>(v)
}
}
}
pub struct ConversionError {
from: &'static str,
expected: &'static str,
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"failed to convert into `{}` from value with type `{:?}`",
self.expected, self.from
)
}
}
/// The action performed on a record
///
/// This is used in live query notifications.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Action {
Create,
Update,
Delete,
}
impl Action {
pub(crate) fn from_core(action: CoreAction) -> Self {
match action {
CoreAction::Create => Self::Create,
CoreAction::Update => Self::Update,
CoreAction::Delete => Self::Delete,
_ => panic!("unimplemented variant of action"),
}
}
}
/// A live query notification
///
/// Live queries return a stream of notifications. The notification contains an `action` that triggered the change in the database record and `data` itself.
/// For deletions the data is the record before it was deleted. For everything else, it's the newly created record or updated record depending on whether
/// the action is create or update.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub struct Notification<R> {
pub query_id: Uuid,
pub action: Action,
pub data: R,
}
impl Notification<CoreValue> {
pub fn map_deserialize<R>(self) -> Result<Notification<R>, crate::error::Db>
where
R: DeserializeOwned,
{
let data = surrealdb_core::sql::from_value(self.data)?;
Ok(Notification {
query_id: self.query_id,
action: self.action,
data,
})
}
}

244
lib/src/api/value/obj.rs Normal file
View file

@ -0,0 +1,244 @@
use std::{
borrow::Borrow,
collections::btree_map::{IntoIter as BIntoIter, Iter as BIter, IterMut as BIterMut},
iter::FusedIterator,
};
use surrealdb_core::sql::{Object as CoreObject, Value as CoreValue};
use super::Value;
transparent_wrapper! {
#[derive( Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
pub struct Object(CoreObject)
}
impl_serialize_wrapper!(Object);
impl Object {
pub fn new() -> Self {
Object(CoreObject::default())
}
pub fn clear(&mut self) {
self.0.clear()
}
pub fn get<Q>(&self, key: &Q) -> Option<&Value>
where
String: Borrow<Q>,
Q: Ord + ?Sized,
{
self.0.get(key).map(Value::from_inner_ref)
}
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
where
String: Borrow<Q>,
Q: Ord + ?Sized,
{
self.0.get_mut(key).map(Value::from_inner_mut)
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
String: Borrow<Q>,
Q: ?Sized + Ord,
{
self.0.contains_key(key)
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<Value>
where
String: Borrow<Q>,
Q: ?Sized + Ord,
{
self.0.remove(key).map(Value::from_inner)
}
pub fn remove_entry<Q>(&mut self, key: &Q) -> Option<(String, Value)>
where
String: Borrow<Q>,
Q: ?Sized + Ord,
{
self.0.remove_entry(key).map(|(a, b)| (a, Value::from_inner(b)))
}
pub fn iter(&self) -> Iter<'_> {
Iter {
iter: self.0.iter(),
}
}
pub fn iter_mut(&mut self) -> IterMut<'_> {
IterMut {
iter: self.0.iter_mut(),
}
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn insert<V>(&mut self, key: String, value: V) -> Option<Value>
where
V: Into<Value>,
{
self.0.insert(key, value.into().into_inner()).map(Value::from_inner)
}
}
pub struct IntoIter {
iter: BIntoIter<String, CoreValue>,
}
impl Iterator for IntoIter {
type Item = (String, Value);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|x| (x.0, Value::from_inner(x.1)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl DoubleEndedIterator for IntoIter {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().map(|x| (x.0, Value::from_inner(x.1)))
}
}
impl ExactSizeIterator for IntoIter {
fn len(&self) -> usize {
self.iter.len()
}
}
impl FusedIterator for IntoIter {}
impl IntoIterator for Object {
type Item = (String, Value);
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
iter: self.0 .0.into_iter(),
}
}
}
#[derive(Clone)]
pub struct Iter<'a> {
iter: BIter<'a, String, CoreValue>,
}
impl<'a> IntoIterator for &'a Object {
type Item = (&'a String, &'a Value);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a String, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(a, b)| (a, Value::from_inner_ref(b)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
self.iter.last().map(|(a, b)| (a, Value::from_inner_ref(b)))
}
fn min(mut self) -> Option<Self::Item> {
self.iter.next().map(|(a, b)| (a, Value::from_inner_ref(b)))
}
fn max(mut self) -> Option<Self::Item> {
self.iter.next_back().map(|(a, b)| (a, Value::from_inner_ref(b)))
}
}
impl FusedIterator for Iter<'_> {}
impl<'a> DoubleEndedIterator for Iter<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().map(|(a, b)| (a, Value::from_inner_ref(b)))
}
}
impl<'a> ExactSizeIterator for Iter<'a> {
fn len(&self) -> usize {
self.iter.len()
}
}
pub struct IterMut<'a> {
iter: BIterMut<'a, String, CoreValue>,
}
impl<'a> IntoIterator for &'a mut Object {
type Item = (&'a String, &'a mut Value);
type IntoIter = IterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<'a> Iterator for IterMut<'a> {
type Item = (&'a String, &'a mut Value);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(a, b)| (a, Value::from_inner_mut(b)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
self.iter.last().map(|(a, b)| (a, Value::from_inner_mut(b)))
}
fn min(mut self) -> Option<Self::Item> {
self.iter.next().map(|(a, b)| (a, Value::from_inner_mut(b)))
}
fn max(mut self) -> Option<Self::Item> {
self.iter.next_back().map(|(a, b)| (a, Value::from_inner_mut(b)))
}
}
impl FusedIterator for IterMut<'_> {}
impl<'a> DoubleEndedIterator for IterMut<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().map(|(a, b)| (a, Value::from_inner_mut(b)))
}
}
impl<'a> ExactSizeIterator for IterMut<'a> {
fn len(&self) -> usize {
self.iter.len()
}
}

View file

@ -116,34 +116,14 @@ compile_error!("The `ml` feature is not supported on the `wasm32` architecture."
#[macro_use]
extern crate tracing;
#[macro_use]
mod mac;
mod api;
#[doc(inline)]
pub use api::engine;
#[cfg(feature = "protocol-http")]
#[doc(hidden)]
pub use api::headers;
#[doc(inline)]
pub use api::method;
#[doc(inline)]
pub use api::opt;
#[doc(inline)]
pub use api::Connect;
#[doc(inline)]
pub use api::Connection;
#[doc(inline)]
pub use api::Response;
#[doc(inline)]
pub use api::Result;
#[doc(inline)]
pub use api::Surreal;
#[doc(inline)]
pub use surrealdb_core::*;
use uuid::Uuid;
pub use uuid::Uuid;
#[macro_use]
mod mac;
mod api;
#[doc(hidden)]
/// Channels for receiving a SurrealQL database export
@ -157,43 +137,21 @@ pub mod channel {
/// Different error types for embedded and remote databases
pub mod error {
pub use crate::api::err::Error as Api;
pub use crate::err::Error as Db;
pub use surrealdb_core::err::Error as Db;
}
/// The action performed on a record
///
/// This is used in live query notifications.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum Action {
Create,
Update,
Delete,
}
#[cfg(feature = "protocol-http")]
#[doc(hidden)]
pub use api::headers;
impl From<dbs::Action> for Action {
fn from(action: dbs::Action) -> Self {
match action {
dbs::Action::Create => Self::Create,
dbs::Action::Update => Self::Update,
dbs::Action::Delete => Self::Delete,
_ => unreachable!(),
}
}
}
/// A live query notification
///
/// Live queries return a stream of notifications. The notification contains an `action` that triggered the change in the database record and `data` itself.
/// For deletions the data is the record before it was deleted. For everything else, it's the newly created record or updated record depending on whether
/// the action is create or update.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub struct Notification<R> {
pub query_id: Uuid,
pub action: Action,
pub data: R,
}
#[doc(inline)]
pub use api::{
engine, method, opt,
value::{
self, Action, Bytes, Datetime, Notification, Number, Object, RecordId, RecordIdKey, Value,
},
Connect, Connection, Response, Result, Surreal,
};
/// An error originating from the SurrealDB client library
#[derive(Debug, thiserror::Error, serde::Serialize)]

View file

@ -1,9 +1,10 @@
/*
/// Creates a new b-tree map of key-value pairs
macro_rules! map {
($($k:expr $(, if let $grant:pat = $check:expr)? $(, if $guard:expr)? => $v:expr),* $(,)? $( => $x:expr )?) => {{
let mut m = ::std::collections::BTreeMap::new();
$(m.extend($x.iter().map(|(k, v)| (k.clone(), v.clone())));)?
($($k:expr $(, if let $grant:pat = $check:expr)? $(, if $guard:expr)? => $v:expr),* $(,)? $( => $x:expr )?) => {{
let mut m = ::std::collections::BTreeMap::new();
$(m.extend($x.iter().map(|(k, v)| (k.clone(), v.clone())));)?
$( $(if let $grant = $check)? $(if $guard)? { m.insert($k, $v); };)+
m
}};
}
m
}};
}*/

View file

@ -26,10 +26,7 @@ mod api_integration {
use surrealdb::sql::statements::BeginStatement;
use surrealdb::sql::statements::CommitStatement;
use surrealdb::sql::thing;
use surrealdb::sql::Thing;
use surrealdb::sql::Value;
use surrealdb::Error;
use surrealdb::Surreal;
use surrealdb::{Error, RecordId, Surreal, Value};
use tokio::sync::Semaphore;
use tokio::sync::SemaphorePermit;
use tracing_subscriber::filter::EnvFilter;
@ -48,9 +45,9 @@ mod api_integration {
name: String,
}
#[derive(Debug, Clone, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
struct RecordId {
id: Thing,
#[derive(Debug, Clone, Deserialize, PartialEq, PartialOrd)]
struct ApiRecordId {
id: RecordId,
}
#[derive(Debug, Deserialize)]
@ -58,9 +55,9 @@ mod api_integration {
name: String,
}
#[derive(Debug, Clone, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Debug, Clone, Deserialize, PartialEq, PartialOrd)]
struct RecordBuf {
id: Thing,
id: RecordId,
name: String,
}
@ -182,6 +179,7 @@ mod api_integration {
use surrealdb::engine::local::Db;
use surrealdb::engine::local::Mem;
use surrealdb::iam;
use surrealdb::RecordIdKey;
async fn new_db() -> (SemaphorePermit<'static>, Surreal<Db>) {
let permit = PERMITS.acquire().await.unwrap();
@ -213,10 +211,11 @@ mod api_integration {
async fn signin_first_not_necessary() {
let db = Surreal::new::<Mem>(()).await.unwrap();
db.use_ns("namespace").use_db("database").await.unwrap();
let Some(record): Option<RecordId> = db.create(("item", "foo")).await.unwrap() else {
let Some(record): Option<ApiRecordId> = db.create(("item", "foo")).await.unwrap()
else {
panic!("record not found");
};
assert_eq!(record.id.to_string(), "item:foo");
assert_eq!(*record.id.key(), RecordIdKey::from("foo".to_owned()));
}
#[test_log::test(tokio::test)]

View file

@ -10,7 +10,7 @@ async fn export_import() {
let db_name = Ulid::new().to_string();
db.use_ns(NS).use_db(&db_name).await.unwrap();
for i in 0..10 {
let _: Vec<RecordId> = db
let _: Vec<ApiRecordId> = db
.create("user")
.content(Record {
name: format!("User {i}"),

View file

@ -35,9 +35,9 @@ async fn live_select_table() {
let mut users = db.select(&table).live().await.unwrap();
// Create a record
let created: Vec<RecordId> = db.create(table).await.unwrap();
let created: Vec<ApiRecordId> = db.create(table).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// The returned record should match the created record
assert_eq!(created, vec![notification.data.clone()]);
@ -45,18 +45,18 @@ async fn live_select_table() {
assert_eq!(notification.action, Action::Create);
// Update the record
let _: Option<RecordId> =
let _: Option<ApiRecordId> =
db.update(&notification.data.id).content(json!({"foo": "bar"})).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be updated
assert_eq!(notification.action, Action::Update);
// Delete the record
let _: Option<RecordId> = db.delete(&notification.data.id).await.unwrap();
let _: Option<ApiRecordId> = db.delete(&notification.data.id).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> = users.next().await.unwrap().unwrap();
let notification: Notification<ApiRecordId> = users.next().await.unwrap().unwrap();
// It should be deleted
assert_eq!(notification.action, Action::Delete);
}
@ -79,7 +79,7 @@ async fn live_select_table() {
// Pull the notification
let notification = tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap();
// The returned record should be an object
assert!(notification.data.is_object());
assert!(notification.data.into_inner().is_object());
// It should be newly created
assert_eq!(notification.action, Action::Create);
}
@ -102,15 +102,15 @@ async fn live_select_record_id() {
} else {
db.query(format!("DEFINE TABLE {table}")).await.unwrap();
}
let record_id = Thing::from((table, "john".to_owned()));
let record_id = RecordId::from((table, "john".to_owned()));
// Start listening
let mut users = db.select(&record_id).live().await.unwrap();
// Create a record
let created: Option<RecordId> = db.create(record_id).await.unwrap();
let created: Option<ApiRecordId> = db.create(record_id).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// The returned record should match the created record
assert_eq!(created, Some(notification.data.clone()));
@ -118,18 +118,18 @@ async fn live_select_record_id() {
assert_eq!(notification.action, Action::Create);
// Update the record
let _: Option<RecordId> =
let _: Option<ApiRecordId> =
db.update(&notification.data.id).content(json!({"foo": "bar"})).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be updated
assert_eq!(notification.action, Action::Update);
// Delete the record
let _: Option<RecordId> = db.delete(&notification.data.id).await.unwrap();
let _: Option<ApiRecordId> = db.delete(&notification.data.id).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be deleted
assert_eq!(notification.action, Action::Delete);
@ -144,7 +144,7 @@ async fn live_select_record_id() {
} else {
db.query(format!("DEFINE TABLE {table}")).await.unwrap();
}
let record_id = Thing::from((table, "john".to_owned()));
let record_id = RecordId::from((table, "john".to_owned()));
// Start listening
let mut users = db.select(Resource::from(&record_id)).live().await.unwrap();
@ -155,7 +155,7 @@ async fn live_select_record_id() {
let notification: Notification<Value> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap();
// The returned record should be an object
assert!(notification.data.is_object());
assert!(notification.data.into_inner().is_object());
// It should be newly created
assert_eq!(notification.action, Action::Create);
}
@ -183,9 +183,9 @@ async fn live_select_record_ranges() {
let mut users = db.select(&table).range("jane".."john").live().await.unwrap();
// Create a record
let created: Option<RecordId> = db.create((table, "jane")).await.unwrap();
let created: Option<ApiRecordId> = db.create((table, "jane")).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// The returned record should match the created record
assert_eq!(created, Some(notification.data.clone()));
@ -193,19 +193,19 @@ async fn live_select_record_ranges() {
assert_eq!(notification.action, Action::Create);
// Update the record
let _: Option<RecordId> =
let _: Option<ApiRecordId> =
db.update(&notification.data.id).content(json!({"foo": "bar"})).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be updated
assert_eq!(notification.action, Action::Update);
// Delete the record
let _: Option<RecordId> = db.delete(&notification.data.id).await.unwrap();
let _: Option<ApiRecordId> = db.delete(&notification.data.id).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be deleted
@ -227,25 +227,26 @@ async fn live_select_record_ranges() {
db.select(Resource::from(&table)).range("jane".."john").live().await.unwrap();
// Create a record
let created_value = match db.create(Resource::from((table, "job"))).await.unwrap() {
Value::Object(created_value) => created_value,
_ => panic!("Expected an object"),
};
let created_value =
match db.create(Resource::from((table, "job"))).await.unwrap().into_inner() {
CoreValue::Object(created_value) => created_value,
_ => panic!("Expected an object"),
};
// Pull the notification
let notification: Notification<Value> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap();
// The returned record should be an object
assert!(notification.data.is_object());
assert!(notification.data.into_inner().is_object());
// It should be newly created
assert_eq!(notification.action, Action::Create);
// Delete the record
let thing = match created_value.0.get("id").unwrap() {
Value::Thing(thing) => thing,
let thing = match created_value.get("id").unwrap() {
CoreValue::Thing(thing) => thing,
_ => panic!("Expected a thing"),
};
db.query("DELETE $item").bind(("item", thing.clone())).await.unwrap();
db.query("DELETE $item").bind(("item", RecordId::from_inner(thing.clone()))).await.unwrap();
// Pull the notification
let notification: Notification<Value> =
@ -253,11 +254,11 @@ async fn live_select_record_ranges() {
// It should be deleted
assert_eq!(notification.action, Action::Delete);
let notification = match notification.data {
Value::Object(notification) => notification,
let notification = match notification.data.into_inner() {
CoreValue::Object(notification) => notification,
_ => panic!("Expected an object"),
};
assert_eq!(notification.0, created_value.0);
assert_eq!(notification, created_value);
}
drop(permit);
@ -280,7 +281,7 @@ async fn live_select_query() {
// Start listening
info!("Starting live query");
let users: QueryStream<Notification<RecordId>> = db
let users: QueryStream<Notification<ApiRecordId>> = db
.query(format!("LIVE SELECT * FROM {table}"))
.await
.unwrap()
@ -290,7 +291,7 @@ async fn live_select_query() {
// Create a record
info!("Creating record");
let created: Vec<RecordId> = db.create(table).await.unwrap();
let created: Vec<ApiRecordId> = db.create(table).await.unwrap();
// Pull the notification
let notifications = receive_all_pending_notifications(users.clone(), LQ_TIMEOUT).await;
// It should be newly created
@ -305,7 +306,7 @@ async fn live_select_query() {
// Update the record
info!("Updating record");
let _: Option<RecordId> =
let _: Option<ApiRecordId> =
db.update(&notifications[0].data.id).content(json!({"foo": "bar"})).await.unwrap();
let notifications = receive_all_pending_notifications(users.clone(), LQ_TIMEOUT).await;
@ -319,7 +320,7 @@ async fn live_select_query() {
// Delete the record
info!("Deleting record");
let _: Option<RecordId> = db.delete(&notifications[0].data.id).await.unwrap();
let _: Option<ApiRecordId> = db.delete(&notifications[0].data.id).await.unwrap();
// Pull the notification
let notifications = receive_all_pending_notifications(users.clone(), LQ_TIMEOUT).await;
// It should be deleted
@ -349,7 +350,7 @@ async fn live_select_query() {
let notification = tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap();
// The returned record should be an object
assert!(notification.data.is_object());
assert!(notification.data.into_inner().is_object());
// It should be newly created
assert_eq!(notification.action, Action::Create);
}
@ -367,9 +368,9 @@ async fn live_select_query() {
.unwrap();
// Create a record
let created: Vec<RecordId> = db.create(table).await.unwrap();
let created: Vec<ApiRecordId> = db.create(table).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// The returned record should match the created record
assert_eq!(created, vec![notification.data.clone()]);
@ -377,18 +378,18 @@ async fn live_select_query() {
assert_eq!(notification.action, Action::Create, "{:?}", notification);
// Update the record
let _: Option<RecordId> =
let _: Option<ApiRecordId> =
db.update(&notification.data.id).content(json!({"foo": "bar"})).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be updated
assert_eq!(notification.action, Action::Update, "{:?}", notification);
// Delete the record
let _: Option<RecordId> = db.delete(&notification.data.id).await.unwrap();
let _: Option<ApiRecordId> = db.delete(&notification.data.id).await.unwrap();
// Pull the notification
let notification: Notification<RecordId> =
let notification: Notification<ApiRecordId> =
tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap().unwrap();
// It should be deleted
assert_eq!(notification.action, Action::Delete, "{:?}", notification);
@ -413,7 +414,7 @@ async fn live_select_query() {
// Pull the notification
let notification = tokio::time::timeout(LQ_TIMEOUT, users.next()).await.unwrap().unwrap();
// The returned record should be an object
assert!(notification.data.is_object());
assert!(notification.data.into_inner().is_object());
// It should be newly created
assert_eq!(notification.action, Action::Create);
}

View file

@ -1,8 +1,9 @@
// Tests common to all protocols and storage engines
use surrealdb::fflags::FFLAGS;
use surrealdb::sql::value;
use surrealdb::value;
use surrealdb::Response;
use surrealdb_core::sql::{Id, Value as CoreValue};
static PERMITS: Semaphore = Semaphore::const_new(1);
@ -43,7 +44,7 @@ async fn invalidate() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
db.invalidate().await.unwrap();
let error = db.create::<Option<RecordId>>(("user", "john")).await.unwrap_err();
let error = db.create::<Option<ApiRecordId>>(("user", "john")).await.unwrap_err();
assert!(
error.to_string().contains("Not enough permissions to perform this action"),
"Unexpected error: {:?}",
@ -370,7 +371,7 @@ async fn query_binds() {
assert_eq!(record.name, "John Doe");
let mut response = db
.query("SELECT * FROM $record_id")
.bind(("record_id", thing("user:john").unwrap()))
.bind(("record_id", "user:john".parse::<RecordId>().unwrap()))
.await
.unwrap();
let Some(record): Option<RecordName> = response.take(0).unwrap() else {
@ -404,7 +405,7 @@ async fn query_with_stats() {
// Second query statement
let (stats, result) = response.take(1).unwrap();
assert!(stats.execution_time > Some(Duration::ZERO));
let _: Vec<RecordId> = result.unwrap();
let _: Vec<ApiRecordId> = result.unwrap();
}
#[test_log::test(tokio::test)]
@ -432,7 +433,7 @@ async fn mixed_results_query() {
let sql = "CREATE bar SET baz = rand('a'); CREATE foo;";
let mut response = db.query(sql).await.unwrap();
response.take::<Value>(0).unwrap_err();
let _: Option<RecordId> = response.take(1).unwrap();
let _: Option<ApiRecordId> = response.take(1).unwrap();
}
#[test_log::test(tokio::test)]
@ -440,7 +441,7 @@ async fn create_record_no_id() {
let (permit, db) = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let _: Vec<RecordId> = db.create("user").await.unwrap();
let _: Vec<ApiRecordId> = db.create("user").await.unwrap();
let _: Value = db.create(Resource::from("user")).await.unwrap();
}
@ -449,7 +450,7 @@ async fn create_record_with_id() {
let (permit, db) = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let _: Option<RecordId> = db.create(("user", "jane")).await.unwrap();
let _: Option<ApiRecordId> = db.create(("user", "jane")).await.unwrap();
let _: Value = db.create(Resource::from(("user", "john"))).await.unwrap();
let _: Value = db.create(Resource::from("user:doe")).await.unwrap();
}
@ -459,7 +460,7 @@ async fn create_record_no_id_with_content() {
let (permit, db) = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let _: Vec<RecordId> = db
let _: Vec<ApiRecordId> = db
.create("user")
.content(Record {
name: "John Doe".to_owned(),
@ -480,22 +481,25 @@ async fn create_record_with_id_with_content() {
let (permit, db) = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let record: Option<RecordId> = db
let record: Option<ApiRecordId> = db
.create(("user", "john"))
.content(Record {
name: "John Doe".to_owned(),
})
.await
.unwrap();
assert_eq!(record.unwrap().id, thing("user:john").unwrap());
assert_eq!(record.unwrap().id, "user:john".parse::<RecordId>().unwrap());
let value: Value = db
.create(Resource::from("user:jane"))
.create(Resource::from("user:jane".parse::<RecordId>().unwrap()))
.content(Record {
name: "Jane Doe".to_owned(),
})
.await
.unwrap();
assert_eq!(value.record(), thing("user:jane").ok());
assert_eq!(
value.into_inner().record(),
Some("user:jane".parse::<RecordId>().unwrap().into_inner())
);
}
#[test_log::test(tokio::test)]
@ -504,14 +508,14 @@ async fn insert_table() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Vec<RecordId> = db.insert(table).await.unwrap();
let _: Vec<RecordId> = db.insert(table).content(json!({ "foo": "bar" })).await.unwrap();
let _: Vec<RecordId> = db.insert(table).content(json!([{ "foo": "bar" }])).await.unwrap();
let _: Vec<ApiRecordId> = db.insert(table).await.unwrap();
let _: Vec<ApiRecordId> = db.insert(table).content(json!({ "foo": "bar" })).await.unwrap();
let _: Vec<ApiRecordId> = db.insert(table).content(json!([{ "foo": "bar" }])).await.unwrap();
let _: Value = db.insert(Resource::from(table)).await.unwrap();
let _: Value = db.insert(Resource::from(table)).content(json!({ "foo": "bar" })).await.unwrap();
let _: Value =
db.insert(Resource::from(table)).content(json!([{ "foo": "bar" }])).await.unwrap();
let users: Vec<RecordId> = db.insert(table).await.unwrap();
let users: Vec<ApiRecordId> = db.insert(table).await.unwrap();
assert!(!users.is_empty());
}
@ -521,17 +525,17 @@ async fn insert_thing() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Option<RecordId> = db.insert((table, "user1")).await.unwrap();
let _: Option<RecordId> =
let _: Option<ApiRecordId> = db.insert((table, "user1")).await.unwrap();
let _: Option<ApiRecordId> =
db.insert((table, "user1")).content(json!({ "foo": "bar" })).await.unwrap();
let _: Value = db.insert(Resource::from((table, "user2"))).await.unwrap();
let _: Value =
db.insert(Resource::from((table, "user2"))).content(json!({ "foo": "bar" })).await.unwrap();
let user: Option<RecordId> = db.insert((table, "user3")).await.unwrap();
let user: Option<ApiRecordId> = db.insert((table, "user3")).await.unwrap();
assert_eq!(
user,
Some(RecordId {
id: thing("user:user3").unwrap(),
Some(ApiRecordId {
id: "user:user3".parse().unwrap(),
})
);
}
@ -542,10 +546,10 @@ async fn select_table() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let _: Value = db.create(Resource::from(table)).await.unwrap();
let users: Vec<RecordId> = db.select(table).await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).await.unwrap();
assert_eq!(users.len(), 3);
}
@ -555,13 +559,16 @@ async fn select_record_id() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let record_id = ("user", "john");
let _: Option<RecordId> = db.create(record_id).await.unwrap();
let Some(record): Option<RecordId> = db.select(record_id).await.unwrap() else {
let _: Option<ApiRecordId> = db.create(record_id).await.unwrap();
let Some(record): Option<ApiRecordId> = db.select(record_id).await.unwrap() else {
panic!("record not found");
};
assert_eq!(record.id, thing("user:john").unwrap());
assert_eq!(record.id, "user:john".parse().unwrap());
let value: Value = db.select(Resource::from(record_id)).await.unwrap();
assert_eq!(value.record(), thing("user:john").ok());
assert_eq!(
value.into_inner().record(),
Some("user:john".parse::<RecordId>().unwrap().into_inner())
);
}
#[test_log::test(tokio::test)]
@ -570,32 +577,39 @@ async fn select_record_ranges() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Option<RecordId> = db.create((table, "amos")).await.unwrap();
let _: Option<RecordId> = db.create((table, "jane")).await.unwrap();
let _: Option<RecordId> = db.create((table, "john")).await.unwrap();
let _: Option<ApiRecordId> = db.create((table, "amos")).await.unwrap();
let _: Option<ApiRecordId> = db.create((table, "jane")).await.unwrap();
let _: Option<ApiRecordId> = db.create((table, "john")).await.unwrap();
let _: Value = db.create(Resource::from((table, "zoey"))).await.unwrap();
let convert = |users: Vec<RecordId>| -> Vec<String> {
users.into_iter().map(|user| user.id.id.to_string()).collect()
let convert = |users: Vec<ApiRecordId>| -> Vec<String> {
users
.into_iter()
.map(|user| {
let Id::String(ref x) = user.id.into_inner().id else {
panic!()
};
x.clone()
})
.collect()
};
let users: Vec<RecordId> = db.select(table).range(..).await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).range(..).await.unwrap();
assert_eq!(convert(users), vec!["amos", "jane", "john", "zoey"]);
let users: Vec<RecordId> = db.select(table).range(.."john").await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).range(.."john").await.unwrap();
assert_eq!(convert(users), vec!["amos", "jane"]);
let users: Vec<RecordId> = db.select(table).range(..="john").await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).range(..="john").await.unwrap();
assert_eq!(convert(users), vec!["amos", "jane", "john"]);
let users: Vec<RecordId> = db.select(table).range("jane"..).await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).range("jane"..).await.unwrap();
assert_eq!(convert(users), vec!["jane", "john", "zoey"]);
let users: Vec<RecordId> = db.select(table).range("jane".."john").await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).range("jane".."john").await.unwrap();
assert_eq!(convert(users), vec!["jane"]);
let users: Vec<RecordId> = db.select(table).range("jane"..="john").await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).range("jane"..="john").await.unwrap();
assert_eq!(convert(users), vec!["jane", "john"]);
let Value::Array(array): Value =
db.select(Resource::from(table)).range("jane"..="john").await.unwrap()
else {
unreachable!();
let v: Value = db.select(Resource::from(table)).range("jane"..="john").await.unwrap();
let CoreValue::Array(array) = v.into_inner() else {
panic!()
};
assert_eq!(array.len(), 2);
let users: Vec<RecordId> =
let users: Vec<ApiRecordId> =
db.select(table).range((Bound::Excluded("jane"), Bound::Included("john"))).await.unwrap();
assert_eq!(convert(users), vec!["john"]);
}
@ -673,8 +687,8 @@ async fn select_records_fetch() {
let check_fetch = |mut response: Response, expected: &str| {
let val: Value = response.take(0).unwrap();
let exp = value(expected).unwrap();
assert_eq!(format!("{val:#}"), format!("{exp:#}"));
let exp = expected.parse().unwrap();
assert_eq!(val, exp);
};
let sql = "SELECT * FROM person LIMIT 1 FETCH tags;";
@ -760,10 +774,10 @@ async fn update_table() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let _: Value = db.update(Resource::from(table)).await.unwrap();
let users: Vec<RecordId> = db.update(table).await.unwrap();
let users: Vec<ApiRecordId> = db.update(table).await.unwrap();
assert_eq!(users.len(), 2);
}
@ -773,9 +787,9 @@ async fn update_record_id() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Option<RecordId> = db.create((table, "john")).await.unwrap();
let _: Option<RecordId> = db.create((table, "jane")).await.unwrap();
let users: Vec<RecordId> = db.update(table).await.unwrap();
let _: Option<ApiRecordId> = db.create((table, "john")).await.unwrap();
let _: Option<ApiRecordId> = db.create((table, "jane")).await.unwrap();
let users: Vec<ApiRecordId> = db.update(table).await.unwrap();
assert_eq!(users.len(), 2);
}
@ -802,19 +816,19 @@ async fn update_table_with_content() {
.unwrap();
let expected = &[
RecordBuf {
id: thing("user:amos").unwrap(),
id: "user:amos".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:jane").unwrap(),
id: "user:jane".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:john").unwrap(),
id: "user:john".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:zoey").unwrap(),
id: "user:zoey".parse().unwrap(),
name: "Doe".to_owned(),
},
];
@ -849,11 +863,11 @@ async fn update_record_range_with_content() {
users,
&[
RecordBuf {
id: thing("user:jane").unwrap(),
id: "user:jane".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:john").unwrap(),
id: "user:john".parse().unwrap(),
name: "Doe".to_owned(),
},
]
@ -863,19 +877,19 @@ async fn update_record_range_with_content() {
users,
&[
RecordBuf {
id: thing("user:amos").unwrap(),
id: "user:amos".parse().unwrap(),
name: "Amos".to_owned(),
},
RecordBuf {
id: thing("user:jane").unwrap(),
id: "user:jane".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:john").unwrap(),
id: "user:john".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:zoey").unwrap(),
id: "user:zoey".parse().unwrap(),
name: "Zoey".to_owned(),
},
]
@ -914,10 +928,10 @@ struct Name {
last: Cow<'static, str>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
struct Person {
#[serde(skip_serializing)]
id: Option<Thing>,
id: Option<RecordId>,
title: Cow<'static, str>,
name: Name,
marketing: bool,
@ -942,14 +956,14 @@ async fn merge_record_id() {
})
.await
.unwrap();
assert_eq!(jaime.unwrap().id.unwrap(), thing("person:jaime").unwrap());
assert_eq!(jaime.unwrap().id.unwrap(), "person:jaime".parse().unwrap());
jaime = db.update(record_id).merge(json!({ "marketing": true })).await.unwrap();
assert!(jaime.as_ref().unwrap().marketing);
jaime = db.select(record_id).await.unwrap();
assert_eq!(
jaime.unwrap(),
Person {
id: Some(thing("person:jaime").unwrap()),
id: Some("person:jaime".parse().unwrap()),
title: "Founder & COO".into(),
name: Name {
first: "Jaime".into(),
@ -962,9 +976,9 @@ async fn merge_record_id() {
#[test_log::test(tokio::test)]
async fn patch_record_id() {
#[derive(Debug, Deserialize, Eq, PartialEq)]
#[derive(Debug, Deserialize, PartialEq)]
struct Record {
id: Thing,
id: RecordId,
baz: String,
hello: Vec<String>,
}
@ -973,7 +987,7 @@ async fn patch_record_id() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let id = "john";
let _: Option<RecordId> = db
let _: Option<ApiRecordId> = db
.create(("user", id))
.content(json!({
"baz": "qux",
@ -992,7 +1006,7 @@ async fn patch_record_id() {
assert_eq!(
value,
Some(Record {
id: thing(&format!("user:{id}")).unwrap(),
id: format!("user:{id}").parse().unwrap(),
baz: "boo".to_owned(),
hello: vec!["world".to_owned()],
})
@ -1005,14 +1019,14 @@ async fn delete_table() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let table = "user";
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let _: Vec<RecordId> = db.create(table).await.unwrap();
let users: Vec<RecordId> = db.select(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let _: Vec<ApiRecordId> = db.create(table).await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).await.unwrap();
assert_eq!(users.len(), 3);
let users: Vec<RecordId> = db.delete(table).await.unwrap();
let users: Vec<ApiRecordId> = db.delete(table).await.unwrap();
assert_eq!(users.len(), 3);
let users: Vec<RecordId> = db.select(table).await.unwrap();
let users: Vec<ApiRecordId> = db.select(table).await.unwrap();
assert!(users.is_empty());
}
@ -1022,17 +1036,17 @@ async fn delete_record_id() {
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
drop(permit);
let record_id = ("user", "john");
let _: Option<RecordId> = db.create(record_id).await.unwrap();
let _: Option<RecordId> = db.select(record_id).await.unwrap();
let john: Option<RecordId> = db.delete(record_id).await.unwrap();
let _: Option<ApiRecordId> = db.create(record_id).await.unwrap();
let _: Option<ApiRecordId> = db.select(record_id).await.unwrap();
let john: Option<ApiRecordId> = db.delete(record_id).await.unwrap();
assert!(john.is_some());
let john: Option<RecordId> = db.select(record_id).await.unwrap();
let john: Option<ApiRecordId> = db.select(record_id).await.unwrap();
assert!(john.is_none());
// non-existing user
let jane: Option<RecordId> = db.delete(("user", "jane")).await.unwrap();
let jane: Option<ApiRecordId> = db.delete(("user", "jane")).await.unwrap();
assert!(jane.is_none());
let value = db.delete(Resource::from(("user", "jane"))).await.unwrap();
assert_eq!(value, Value::None);
let value: Value = db.delete(Resource::from(("user", "jane"))).await.unwrap();
assert_eq!(value.into_inner(), CoreValue::None);
}
#[test_log::test(tokio::test)]
@ -1054,11 +1068,11 @@ async fn delete_record_range() {
users,
&[
RecordBuf {
id: thing("user:jane").unwrap(),
id: "user:jane".parse().unwrap(),
name: "Jane".to_owned(),
},
RecordBuf {
id: thing("user:john").unwrap(),
id: "user:john".parse().unwrap(),
name: "John".to_owned(),
},
]
@ -1068,11 +1082,11 @@ async fn delete_record_range() {
users,
&[
RecordBuf {
id: thing("user:amos").unwrap(),
id: "user:amos".parse().unwrap(),
name: "Amos".to_owned(),
},
RecordBuf {
id: thing("user:zoey").unwrap(),
id: "user:zoey".parse().unwrap(),
name: "Zoey".to_owned(),
},
]
@ -1108,11 +1122,11 @@ async fn changefeed() {
.unwrap();
let expected = &[
RecordBuf {
id: thing("user:amos").unwrap(),
id: "user:amos".parse().unwrap(),
name: "Doe".to_owned(),
},
RecordBuf {
id: thing("user:jane").unwrap(),
id: "user:jane".parse().unwrap(),
name: "Doe".to_owned(),
},
];
@ -1123,48 +1137,46 @@ async fn changefeed() {
SHOW CHANGES FOR TABLE user SINCE 0 LIMIT 10;
";
let mut response = db.query(sql).await.unwrap();
let value: Value = response.take(0).unwrap();
let Value::Array(array) = value.clone() else {
unreachable!()
let v: Value = response.take(0).unwrap();
let CoreValue::Array(array) = v.into_inner() else {
panic!()
};
assert_eq!(array.len(), 5);
// DEFINE TABLE
let a = array.first().unwrap();
let Value::Object(a) = a else {
let CoreValue::Object(a) = a.clone() else {
unreachable!()
};
let Value::Number(_versionstamp1) = a.get("versionstamp").unwrap() else {
let CoreValue::Number(_versionstamp1) = a.get("versionstamp").clone().unwrap() else {
unreachable!()
};
let changes = a.get("changes").unwrap().to_owned();
let changes = a.get("changes").unwrap().clone().to_owned();
assert_eq!(
changes,
surrealdb::sql::value(
"[
Value::from_inner(changes),
"[
{
define_table: {
name: 'user'
}
}
]"
)
.parse()
.unwrap()
);
// UPDATE user:amos
let a = array.get(1).unwrap();
let Value::Object(a) = a else {
let CoreValue::Object(a) = a.clone() else {
unreachable!()
};
let Value::Number(versionstamp1) = a.get("versionstamp").unwrap() else {
let CoreValue::Number(versionstamp1) = a.get("versionstamp").unwrap() else {
unreachable!()
};
let changes = a.get("changes").unwrap().to_owned();
match FFLAGS.change_feed_live_queries.enabled() {
true => {
assert_eq!(
changes,
surrealdb::sql::value(
r#"[
Value::from_inner(changes),
r#"[
{
create: {
id: user:amos,
@ -1172,15 +1184,14 @@ async fn changefeed() {
}
}
]"#
)
.parse()
.unwrap()
);
}
false => {
assert_eq!(
changes,
surrealdb::sql::value(
r#"[
Value::from_inner(changes),
r#"[
{
update: {
id: user:amos,
@ -1188,27 +1199,26 @@ async fn changefeed() {
}
}
]"#
)
.parse()
.unwrap()
);
}
}
// UPDATE user:jane
let a = array.get(2).unwrap();
let Value::Object(a) = a else {
let CoreValue::Object(a) = a.clone() else {
unreachable!()
};
let Value::Number(versionstamp2) = a.get("versionstamp").unwrap() else {
let CoreValue::Number(versionstamp2) = a.get("versionstamp").unwrap().clone() else {
unreachable!()
};
assert!(versionstamp1 < versionstamp2);
assert!(*versionstamp1 < versionstamp2);
let changes = a.get("changes").unwrap().to_owned();
match FFLAGS.change_feed_live_queries.enabled() {
true => {
assert_eq!(
changes,
surrealdb::sql::value(
"[
Value::from_inner(changes),
"[
{
create: {
id: user:jane,
@ -1216,15 +1226,14 @@ async fn changefeed() {
}
}
]"
)
.parse()
.unwrap()
);
}
false => {
assert_eq!(
changes,
surrealdb::sql::value(
"[
Value::from_inner(changes),
"[
{
update: {
id: user:jane,
@ -1232,27 +1241,26 @@ async fn changefeed() {
}
}
]"
)
.parse()
.unwrap()
);
}
}
// UPDATE user:amos
let a = array.get(3).unwrap();
let Value::Object(a) = a else {
let CoreValue::Object(a) = a.clone() else {
unreachable!()
};
let Value::Number(versionstamp3) = a.get("versionstamp").unwrap() else {
let CoreValue::Number(versionstamp3) = a.get("versionstamp").unwrap() else {
unreachable!()
};
assert!(versionstamp2 < versionstamp3);
assert!(versionstamp2 < *versionstamp3);
let changes = a.get("changes").unwrap().to_owned();
match FFLAGS.change_feed_live_queries.enabled() {
true => {
assert_eq!(
changes,
surrealdb::sql::value(
"[
Value::from_inner(changes),
"[
{
create: {
id: user:amos,
@ -1260,15 +1268,14 @@ async fn changefeed() {
}
}
]"
)
.parse()
.unwrap()
);
}
false => {
assert_eq!(
changes,
surrealdb::sql::value(
"[
Value::from_inner(changes),
"[
{
update: {
id: user:amos,
@ -1276,25 +1283,24 @@ async fn changefeed() {
}
}
]"
)
.parse()
.unwrap()
);
}
};
// UPDATE table
let a = array.get(4).unwrap();
let Value::Object(a) = a else {
let CoreValue::Object(a) = a.clone() else {
unreachable!()
};
let Value::Number(versionstamp4) = a.get("versionstamp").unwrap() else {
let CoreValue::Number(versionstamp4) = a.get("versionstamp").unwrap() else {
unreachable!()
};
assert!(versionstamp3 < versionstamp4);
let changes = a.get("changes").unwrap().to_owned();
assert_eq!(
changes,
surrealdb::sql::value(
"[
Value::from_inner(changes),
"[
{
update: {
id: user:amos,
@ -1308,7 +1314,7 @@ async fn changefeed() {
}
}
]"
)
.parse()
.unwrap()
);
}
@ -1354,9 +1360,12 @@ async fn return_bool() {
assert!(boolean);
let mut response = db.query("RETURN false").await.unwrap();
let value: Value = response.take(0).unwrap();
assert_eq!(value, Value::Bool(false));
assert_eq!(value.into_inner(), CoreValue::Bool(false));
}
/*
* TODO: Reenable test.
* Disabling run test for now as it depends on value conversions which are removed
#[test_log::test(tokio::test)]
async fn run() {
let (permit, db) = new_db().await;
@ -1395,3 +1404,4 @@ async fn run() {
let tmp = db.run("fn::baz", ()).await.unwrap();
assert_eq!(tmp, Value::from(7));
}
*/

View file

@ -2,10 +2,10 @@ mod parse;
use parse::Parse;
mod helpers;
use helpers::new_ds;
use surrealdb::dbs::Session;
use surrealdb::err::Error;
use surrealdb::iam::Role;
use surrealdb::sql::Value;
use surrealdb_core::dbs::Session;
use surrealdb_core::err::Error;
use surrealdb_core::iam::Role;
use surrealdb_core::sql::Value;
#[tokio::test]
async fn select_field_value() -> Result<(), Error> {
@ -866,6 +866,8 @@ async fn common_permissions_checks(auth_enabled: bool) {
];
let statement = "SELECT * FROM person";
let empty_array = Value::parse("[]");
for ((level, role), (ns, db), should_succeed, msg) in tests.into_iter() {
let sess = Session::for_level(level, role).with_ns(ns).with_db(db);
@ -879,7 +881,7 @@ async fn common_permissions_checks(auth_enabled: bool) {
.unwrap();
let res = resp.remove(0).output();
assert!(
res.is_ok() && res.unwrap() != Value::parse("[]"),
res.is_ok() && res.unwrap() != empty_array,
"unexpected error creating person record"
);
let mut resp = ds
@ -888,7 +890,7 @@ async fn common_permissions_checks(auth_enabled: bool) {
.unwrap();
let res = resp.remove(0).output();
assert!(
res.is_ok() && res.unwrap() != Value::parse("[]"),
res.is_ok() && res.unwrap() != empty_array,
"unexpected error creating person record"
);
let mut resp = ds
@ -897,7 +899,7 @@ async fn common_permissions_checks(auth_enabled: bool) {
.unwrap();
let res = resp.remove(0).output();
assert!(
res.is_ok() && res.unwrap() != Value::parse("[]"),
res.is_ok() && res.unwrap() != empty_array,
"unexpected error creating person record"
);
@ -909,9 +911,9 @@ async fn common_permissions_checks(auth_enabled: bool) {
assert!(res.is_ok());
if should_succeed {
assert!(res.unwrap() != Value::parse("[]"), "{}", msg);
assert!(res.unwrap() != empty_array, "{}", msg);
} else {
assert!(res.unwrap() == Value::parse("[]"), "{}", msg);
assert!(res.unwrap() == empty_array, "{}", msg);
}
}
}

View file

@ -14,8 +14,8 @@ use serde_json::ser::PrettyFormatter;
use surrealdb::engine::any::{connect, IntoEndpoint};
use surrealdb::method::{Stats, WithStats};
use surrealdb::opt::{capabilities::Capabilities, Config};
use surrealdb::sql::{self, Param, Statement, Value};
use surrealdb::{Notification, Response};
use surrealdb::sql::{self, Param, Statement, Uuid as CoreUuid, Value as CoreValue};
use surrealdb::{Notification, Response, Value};
#[derive(Args, Debug)]
pub struct SqlCommandArguments {
@ -207,7 +207,7 @@ pub async fn init(
}
for var in &vars {
query.push(Statement::Value(Value::Param(Param::from(var.as_str()))))
query.push(Statement::Value(CoreValue::Param(Param::from(var.as_str()))))
}
// Extract the namespace and database from the current prompt
@ -284,7 +284,7 @@ fn process(
format!("Expected some result for a query with index {index}, but found none")
})
.map_err(Error::Other)?;
let output = result.unwrap_or_else(|e| e.to_string().into());
let output = result.unwrap_or_else(|e| Value::from_inner(CoreValue::from(e.to_string())));
vec.push((stats, output));
}
@ -306,10 +306,10 @@ fn process(
let message = match (json, pretty) {
// Don't prettify the SurrealQL response
(false, false) => {
let value = Value::from(map! {
String::from("id") => query_id.into(),
let value = CoreValue::from(map! {
String::from("id") => CoreValue::from(CoreUuid::from(query_id)),
String::from("action") => format!("{action:?}").to_ascii_uppercase().into(),
String::from("result") => data,
String::from("result") => data.into_inner(),
});
value.to_string()
}
@ -319,10 +319,10 @@ fn process(
),
// Don't pretty print the JSON response
(true, false) => {
let value = Value::from(map! {
String::from("id") => query_id.into(),
let value = CoreValue::from(map! {
String::from("id") => CoreValue::from(CoreUuid::from(query_id)),
String::from("action") => format!("{action:?}").to_ascii_uppercase().into(),
String::from("result") => data,
String::from("result") => data.into_inner(),
});
value.into_json().to_string()
}
@ -333,7 +333,7 @@ fn process(
&mut buf,
PrettyFormatter::with_indent(b"\t"),
);
data.into_json().serialize(&mut serializer).unwrap();
data.into_inner().into_json().serialize(&mut serializer).unwrap();
let output = String::from_utf8(buf).unwrap();
format!("-- Notification (action: {action:?}, live query ID: {query_id})\n{output:#}")
}
@ -346,7 +346,8 @@ fn process(
Ok(match (json, pretty) {
// Don't prettify the SurrealQL response
(false, false) => {
Value::from(vec.into_iter().map(|(_, x)| x).collect::<Vec<_>>()).to_string()
CoreValue::from(vec.into_iter().map(|(_, x)| x.into_inner()).collect::<Vec<_>>())
.to_string()
}
// Yes prettify the SurrealQL response
(false, true) => vec
@ -361,7 +362,8 @@ fn process(
.join("\n"),
// Don't pretty print the JSON response
(true, false) => {
let value = Value::from(vec.into_iter().map(|(_, x)| x).collect::<Vec<_>>());
let value =
CoreValue::from(vec.into_iter().map(|(_, x)| x.into_inner()).collect::<Vec<_>>());
serde_json::to_string(&value.into_json()).unwrap()
}
// Yes prettify the JSON response
@ -374,7 +376,7 @@ fn process(
&mut buf,
PrettyFormatter::with_indent(b"\t"),
);
value.into_json().serialize(&mut serializer).unwrap();
value.into_inner().into_json().serialize(&mut serializer).unwrap();
let output = String::from_utf8(buf).unwrap();
let query_num = index + 1;
let execution_time = stats.execution_time.unwrap_or_default();

View file

@ -395,8 +395,8 @@ impl RpcContext for Connection {
&mut self.vars
}
fn version_data(&self) -> impl Into<Data> {
format!("{PKG_NAME}-{}", *PKG_VERSION)
fn version_data(&self) -> Data {
format!("{PKG_NAME}-{}", *PKG_VERSION).into()
}
const LQ_SUPPORT: bool = true;

View file

@ -2,15 +2,16 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use crate::cnf::{PKG_NAME, PKG_VERSION};
use surrealdb::dbs::Session;
use surrealdb_core::dbs::Session;
use surrealdb_core::kvs::Datastore;
use surrealdb_core::rpc::Data;
use surrealdb_core::rpc::RpcContext;
use surrealdb_core::rpc::RpcError;
use surrealdb_core::sql::Array;
use surrealdb_core::sql::Value;
#[cfg(surrealdb_unstable)]
use surrealdb::gql::{Pessimistic, SchemaCache};
use surrealdb::kvs::Datastore;
use surrealdb::rpc::Data;
use surrealdb::rpc::RpcContext;
use surrealdb::rpc::RpcError;
use surrealdb::sql::Array;
use surrealdb::sql::Value;
use surrealdb_core::gql::{Pessimistic, SchemaCache};
pub struct PostRpcContext {
pub kvs: Arc<Datastore>,
@ -53,9 +54,8 @@ impl RpcContext for PostRpcContext {
&mut self.vars
}
fn version_data(&self) -> impl Into<Data> {
let val: Value = format!("{PKG_NAME}-{}", *PKG_VERSION).into();
val
fn version_data(&self) -> Data {
Value::from(format!("{PKG_NAME}-{}", *PKG_VERSION)).into()
}
#[cfg(surrealdb_unstable)]
@ -68,14 +68,12 @@ impl RpcContext for PostRpcContext {
// disable:
// doesn't do anything so shouldn't be supported
async fn set(&mut self, _params: Array) -> Result<impl Into<Data>, RpcError> {
let out: Result<Value, RpcError> = Err(RpcError::MethodNotFound);
out
async fn set(&mut self, _params: Array) -> Result<Data, RpcError> {
Err(RpcError::MethodNotFound)
}
// doesn't do anything so shouldn't be supported
async fn unset(&mut self, _params: Array) -> Result<impl Into<Data>, RpcError> {
let out: Result<Value, RpcError> = Err(RpcError::MethodNotFound);
out
async fn unset(&mut self, _params: Array) -> Result<Data, RpcError> {
Err(RpcError::MethodNotFound)
}
}