Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
ac421c24f8 | |||
c8507bb500 |
53 changed files with 676 additions and 1035 deletions
2
.github/workflows/automoderator.yaml
vendored
2
.github/workflows/automoderator.yaml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
|||
const regexReplacements = [
|
||||
// File Sharing
|
||||
{
|
||||
pattern: /(www\.)?(box|dropbox|mediafire|sugarsync|tresorit|hightail|opentext|sharefile|citrixsharefile|icloud|onedrive|1drv|mega)(\.com|\.co\.nz)\/[^\s\)]+/g,
|
||||
pattern: /(www\.)?(box|dropbox|mediafire|sugarsync|tresorit|hightail|opentext|sharefile|citrixsharefile|icloud|onedrive|1drv)\.com\/[^\s\)]+/g,
|
||||
replacement: '[REDACTED]'
|
||||
},
|
||||
// Google Drive
|
||||
|
|
|
@ -684,7 +684,7 @@ jobs:
|
|||
# Update the version to a nightly one
|
||||
sed -i "s#^version = \".*\"#version = \"${version}\"#" sdk/Cargo.toml
|
||||
sed -i "s#^version = \".*\"#version = \"${version}\"#" core/Cargo.toml
|
||||
sed -i "s#surrealdb-core = { version = \"=${currentVersion}\"#surrealdb-core = { version = \"=${version}\"#" sdk/Cargo.toml
|
||||
sed -i "s#surrealdb-core = { version = \"${major}\"#surrealdb-core = { version = \"=${version}\"#" sdk/Cargo.toml
|
||||
|
||||
- name: Patch crate name and description
|
||||
if: ${{ inputs.environment != 'stable' }}
|
||||
|
|
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -3503,17 +3503,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nanoservices-utils"
|
||||
version = "0.1.5"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63a4117f6ebfcfe84722ef283fd6e3bfcaa141718f3ff092343bb6cb98482e03"
|
||||
checksum = "c6a95cdb2ba5dc6584099b1aac8da0a6f80e12ad5f9381a19d232c1fee714793"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"bincode",
|
||||
"bitcode",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"futures",
|
||||
"jsonwebtoken",
|
||||
"paste",
|
||||
"revision 0.7.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5983,9 +5990,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "surrealcs"
|
||||
version = "0.3.2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b28a50184a9a743ac180985d8b94ac4c93e4afaa0140f1057d3b11d4d36b9e8b"
|
||||
checksum = "457eb2adc59f9a9ba337e1fa3a6af63e09524e4adf01921b9d7079917cd7f694"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bytes",
|
||||
|
@ -6004,9 +6011,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "surrealcs-kernel"
|
||||
version = "0.3.2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2a5ba4af373b48fa241a81676d0172fc92e563df217282c472252b90beafee"
|
||||
checksum = "f4a79da58bfc886b93a431a463f956184d4b0a9d3672833544a1b718ff86b0df"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
|
|
|
@ -121,7 +121,7 @@ surrealdb = { version = "2", path = "sdk", features = [
|
|||
surrealdb-core = { version = "2", path = "core" }
|
||||
tempfile = "3.8.1"
|
||||
thiserror = "1.0.63"
|
||||
tokio = { version = "1.40.0", features = ["macros", "signal"] }
|
||||
tokio = { version = "1", features = ["macros", "signal"] }
|
||||
tokio-stream = "0.1"
|
||||
tokio-tungstenite = "0.23.1"
|
||||
tokio-util = { version = "0.7.11", features = ["io"] }
|
||||
|
|
|
@ -147,7 +147,7 @@ sha2 = "0.10.8"
|
|||
snap = "1.1.0"
|
||||
storekey = "0.5.0"
|
||||
subtle = "2.6"
|
||||
surrealcs = { version = "0.3.2", optional = true }
|
||||
surrealcs = { version = "0.3.1", optional = true }
|
||||
surrealkv = { version = "0.3.6", optional = true }
|
||||
surrealml = { version = "0.1.1", optional = true, package = "surrealml-core" }
|
||||
tempfile = { version = "3.10.1", optional = true }
|
||||
|
@ -168,14 +168,14 @@ serial_test = "2.0.0"
|
|||
temp-dir = "0.1.11"
|
||||
test-log = { version = "0.2.13", features = ["trace"] }
|
||||
time = { version = "0.3.36", features = ["serde"] }
|
||||
tokio = { version = "1.40.0", features = ["macros", "sync", "rt-multi-thread"] }
|
||||
tokio = { version = "1", features = ["macros", "sync", "rt-multi-thread"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
wiremock = "0.6.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
pharos = "0.5.3"
|
||||
ring = { version = "0.17.7", features = ["wasm32_unknown_unknown_js"] }
|
||||
tokio = { version = "1.40.0", default-features = false, features = [
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"rt",
|
||||
"sync",
|
||||
] }
|
||||
|
@ -187,7 +187,7 @@ wasmtimer = { version = "0.2.0", default-features = false, features = [
|
|||
ws_stream_wasm = "0.7.4"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1.40.0", default-features = false, features = [
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"macros",
|
||||
"io-util",
|
||||
"io-std",
|
||||
|
|
|
@ -387,12 +387,8 @@ impl<'a> Executor<'a> {
|
|||
.await;
|
||||
ctx = MutableContext::unfreeze(c)?;
|
||||
// Check if this is a RETURN statement
|
||||
let can_return = matches!(
|
||||
stm,
|
||||
Statement::Output(_)
|
||||
| Statement::Value(_) | Statement::Ifelse(_)
|
||||
| Statement::Foreach(_)
|
||||
);
|
||||
let can_return =
|
||||
matches!(stm, Statement::Output(_) | Statement::Value(_));
|
||||
// Catch global timeout
|
||||
let res = match ctx.is_timedout() {
|
||||
true => Err(Error::QueryTimedout),
|
||||
|
|
|
@ -188,7 +188,7 @@ impl Document {
|
|||
// The out field is a match, so don't error
|
||||
Value::Thing(v) if v.eq(r) => (),
|
||||
// The out is a match, so don't error
|
||||
v if r.id.is(&v) => (),
|
||||
v if l.id.is(&v) => (),
|
||||
// The out field does not match
|
||||
v => {
|
||||
return Err(Error::OutMismatch {
|
||||
|
|
|
@ -983,11 +983,9 @@ pub enum Error {
|
|||
index: String,
|
||||
},
|
||||
|
||||
/// The token has expired
|
||||
#[error("The token has expired")]
|
||||
ExpiredToken,
|
||||
|
||||
/// The session has expired
|
||||
/// The session has expired either because the token used
|
||||
/// to establish it has expired or because an expiration
|
||||
/// was explicitly defined when establishing it
|
||||
#[error("The session has expired")]
|
||||
ExpiredSession,
|
||||
|
||||
|
@ -1151,10 +1149,6 @@ pub enum Error {
|
|||
|
||||
#[error("Found a non-computed value where they are not allowed")]
|
||||
NonComputed,
|
||||
|
||||
/// Represents a failure in timestamp arithmetic related to database internals
|
||||
#[error("Failed to compute: \"{0}\", as the operation results in an overflow.")]
|
||||
ArithmeticOverflow(String),
|
||||
}
|
||||
|
||||
impl From<Error> for String {
|
||||
|
|
|
@ -153,19 +153,7 @@ pub fn time((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> {
|
|||
|
||||
pub fn ulid((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
|
||||
let ulid = match timestamp {
|
||||
Some(timestamp) => {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if timestamp.0 < chrono::DateTime::UNIX_EPOCH {
|
||||
return Err(Error::InvalidArguments {
|
||||
name: String::from("rand::ulid"),
|
||||
message: format!(
|
||||
"To generate a ULID from a datetime, it must be a time beyond UNIX epoch."
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
Ulid::from_datetime(timestamp.0.into())
|
||||
}
|
||||
Some(timestamp) => Ulid::from_datetime(timestamp.0.into()),
|
||||
None => Ulid::new(),
|
||||
};
|
||||
|
||||
|
@ -174,19 +162,7 @@ pub fn ulid((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
|
|||
|
||||
pub fn uuid((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
|
||||
let uuid = match timestamp {
|
||||
Some(timestamp) => {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if timestamp.0 < chrono::DateTime::UNIX_EPOCH {
|
||||
return Err(Error::InvalidArguments {
|
||||
name: String::from("rand::ulid"),
|
||||
message: format!(
|
||||
"To generate a ULID from a datetime, it must be a time beyond UNIX epoch."
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
Uuid::new_v7_from_datetime(timestamp)
|
||||
}
|
||||
Some(timestamp) => Uuid::new_v7_from_datetime(timestamp),
|
||||
None => Uuid::new(),
|
||||
};
|
||||
Ok(uuid.into())
|
||||
|
@ -205,19 +181,7 @@ pub mod uuid {
|
|||
|
||||
pub fn v7((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
|
||||
let uuid = match timestamp {
|
||||
Some(timestamp) => {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if timestamp.0 < chrono::DateTime::UNIX_EPOCH {
|
||||
return Err(Error::InvalidArguments {
|
||||
name: String::from("rand::ulid"),
|
||||
message: format!(
|
||||
"To generate a ULID from a datetime, it must be a time beyond UNIX epoch."
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
Uuid::new_v7_from_datetime(timestamp)
|
||||
}
|
||||
Some(timestamp) => Uuid::new_v7_from_datetime(timestamp),
|
||||
None => Uuid::new(),
|
||||
};
|
||||
Ok(uuid.into())
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::ctx::Context;
|
||||
use crate::dbs::Options;
|
||||
use crate::doc::CursorDoc;
|
||||
|
@ -148,7 +146,6 @@ pub fn thing((arg1, arg2): (Value, Option<Value>)) -> Result<Value, Error> {
|
|||
Value::Array(v) => v.into(),
|
||||
Value::Object(v) => v.into(),
|
||||
Value::Number(v) => v.into(),
|
||||
Value::Range(v) => v.deref().to_owned().try_into()?,
|
||||
v => v.as_string().into(),
|
||||
},
|
||||
})),
|
||||
|
|
|
@ -185,7 +185,7 @@ pub async fn generate_schema(
|
|||
let desc = current.get("desc");
|
||||
match (asc, desc) {
|
||||
(Some(_), Some(_)) => {
|
||||
return Err("Found both ASC and DESC in order".into());
|
||||
return Err("Found both asc and desc in order".into());
|
||||
}
|
||||
(Some(GqlValue::Enum(a)), None) => {
|
||||
orders.push(order!(asc, a.as_str()))
|
||||
|
@ -281,7 +281,7 @@ pub async fn generate_schema(
|
|||
})
|
||||
},
|
||||
)
|
||||
.description(format!("{}", if let Some(ref c) = &tb.comment { format!("{c}") } else { format!("Generated from table `{}`\nallows querying a table with filters", tb.name) }))
|
||||
.description(format!("Generated from table `{}`{}\nallows querying a table with filters", tb.name, if let Some(ref c) = &tb.comment {format!("\n{c}")} else {"".to_string()}))
|
||||
.argument(limit_input!())
|
||||
.argument(start_input!())
|
||||
.argument(InputValue::new("order", TypeRef::named(&table_order_name)))
|
||||
|
@ -329,11 +329,12 @@ pub async fn generate_schema(
|
|||
},
|
||||
)
|
||||
.description(format!(
|
||||
"{}",
|
||||
"Generated from table `{}`{}\nallows querying a single record in a table by id",
|
||||
tb.name,
|
||||
if let Some(ref c) = &tb.comment {
|
||||
format!("{c}")
|
||||
format!("\n{c}")
|
||||
} else {
|
||||
format!("Generated from table `{}`\nallows querying a single record in a table by ID", tb.name)
|
||||
"".to_string()
|
||||
}
|
||||
))
|
||||
.argument(id_input!()),
|
||||
|
@ -370,20 +371,11 @@ pub async fn generate_schema(
|
|||
table_filter = table_filter
|
||||
.field(InputValue::new(fd.name.to_string(), TypeRef::named(type_filter_name)));
|
||||
|
||||
table_ty_obj = table_ty_obj
|
||||
.field(Field::new(
|
||||
fd.name.to_string(),
|
||||
fd_type,
|
||||
make_table_field_resolver(fd_name.as_str(), fd.kind.clone()),
|
||||
))
|
||||
.description(format!(
|
||||
"{}",
|
||||
if let Some(ref c) = fd.comment {
|
||||
format!("{c}")
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
));
|
||||
table_ty_obj = table_ty_obj.field(Field::new(
|
||||
fd.name.to_string(),
|
||||
fd_type,
|
||||
make_table_field_resolver(fd_name.as_str(), fd.kind.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
types.push(Type::Object(table_ty_obj));
|
||||
|
@ -429,7 +421,7 @@ pub async fn generate_schema(
|
|||
}
|
||||
})
|
||||
})
|
||||
.description("Allows fetching arbitrary records".to_string())
|
||||
.description("allows fetching arbitrary records".to_string())
|
||||
.argument(id_input!()),
|
||||
);
|
||||
|
||||
|
@ -484,7 +476,7 @@ pub async fn generate_schema(
|
|||
schema,
|
||||
"uuid",
|
||||
Kind::Uuid,
|
||||
"String encoded UUID",
|
||||
"a string encoded uuid",
|
||||
"https://datatracker.ietf.org/doc/html/rfc4122"
|
||||
);
|
||||
|
||||
|
|
|
@ -209,14 +209,8 @@ pub async fn db_access(
|
|||
}
|
||||
Err(e) => match e {
|
||||
Error::Thrown(_) => Err(e),
|
||||
e => {
|
||||
debug!("Record user signin query failed: {e}");
|
||||
if *INSECURE_FORWARD_ACCESS_ERRORS {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(Error::AccessRecordSigninQueryFailed)
|
||||
}
|
||||
}
|
||||
e if *INSECURE_FORWARD_ACCESS_ERRORS => Err(e),
|
||||
_ => Err(Error::AccessRecordSigninQueryFailed),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -227,9 +221,6 @@ pub async fn db_access(
|
|||
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
|
||||
if !*EXPERIMENTAL_BEARER_ACCESS {
|
||||
// Return opaque error to avoid leaking the existence of the feature.
|
||||
debug!(
|
||||
"Error attempting to authenticate with disabled bearer access feature"
|
||||
);
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
// Check if the bearer access method supports issuing tokens.
|
||||
|
@ -245,10 +236,7 @@ pub async fn db_access(
|
|||
let gr = match tx.get_db_access_grant(&ns, &db, &ac, &kid).await {
|
||||
Ok(gr) => gr,
|
||||
// Return opaque error to avoid leaking existence of the grant.
|
||||
Err(e) => {
|
||||
debug!("Error retrieving bearer access grant: {e}");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
_ => return Err(Error::InvalidAuth),
|
||||
};
|
||||
// Ensure that the transaction is cancelled.
|
||||
tx.cancel().await?;
|
||||
|
@ -260,7 +248,7 @@ pub async fn db_access(
|
|||
let tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified user from storage.
|
||||
let user = tx.get_db_user(&ns, &db, user).await.map_err(|e| {
|
||||
debug!("Error retrieving user for bearer access to database `{ns}/{db}`: {e}");
|
||||
trace!("Error while authenticating to database `{ns}/{db}`: {e}");
|
||||
// Return opaque error to avoid leaking grant subject existence.
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
|
@ -375,7 +363,7 @@ pub async fn db_user(
|
|||
..Claims::default()
|
||||
};
|
||||
// Log the authenticated database info
|
||||
trace!("Signing in to database `{ns}/{db}`");
|
||||
trace!("Signing in to database `{}`", db);
|
||||
// Create the authentication token
|
||||
let enc = encode(&HEADER, &val, &key);
|
||||
// Set the authentication on the session
|
||||
|
@ -391,11 +379,7 @@ pub async fn db_user(
|
|||
_ => Err(Error::TokenMakingFailed),
|
||||
}
|
||||
}
|
||||
// The password did not verify
|
||||
Err(e) => {
|
||||
debug!("Failed to verify signin credentials for user `{user}` in database `{ns}/{db}`: {e}");
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
_ => Err(Error::InvalidAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -421,9 +405,6 @@ pub async fn ns_access(
|
|||
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
|
||||
if !*EXPERIMENTAL_BEARER_ACCESS {
|
||||
// Return opaque error to avoid leaking the existence of the feature.
|
||||
debug!(
|
||||
"Error attempting to authenticate with disabled bearer access feature"
|
||||
);
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
// Check if the bearer access method supports issuing tokens.
|
||||
|
@ -439,10 +420,7 @@ pub async fn ns_access(
|
|||
let gr = match tx.get_ns_access_grant(&ns, &ac, &kid).await {
|
||||
Ok(gr) => gr,
|
||||
// Return opaque error to avoid leaking existence of the grant.
|
||||
Err(e) => {
|
||||
debug!("Error retrieving bearer access grant: {e}");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
_ => return Err(Error::InvalidAuth),
|
||||
};
|
||||
// Ensure that the transaction is cancelled.
|
||||
tx.cancel().await?;
|
||||
|
@ -453,12 +431,11 @@ pub async fn ns_access(
|
|||
// Create a new readonly transaction.
|
||||
let tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified user from storage.
|
||||
let user =
|
||||
tx.get_ns_user(&ns, user).await.map_err(|e| {
|
||||
debug!("Error retrieving user for bearer access to namespace `{ns}`: {e}");
|
||||
// Return opaque error to avoid leaking grant subject existence.
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
let user = tx.get_ns_user(&ns, user).await.map_err(|e| {
|
||||
trace!("Error while authenticating to namespace `{ns}`: {e}");
|
||||
// Return opaque error to avoid leaking grant subject existence.
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled.
|
||||
tx.cancel().await?;
|
||||
user.roles.clone()
|
||||
|
@ -556,7 +533,7 @@ pub async fn ns_user(
|
|||
..Claims::default()
|
||||
};
|
||||
// Log the authenticated namespace info
|
||||
trace!("Signing in to namespace `{ns}`");
|
||||
trace!("Signing in to namespace `{}`", ns);
|
||||
// Create the authentication token
|
||||
let enc = encode(&HEADER, &val, &key);
|
||||
// Set the authentication on the session
|
||||
|
@ -572,12 +549,7 @@ pub async fn ns_user(
|
|||
}
|
||||
}
|
||||
// The password did not verify
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"Failed to verify signin credentials for user `{user}` in namespace `{ns}`: {e}"
|
||||
);
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
_ => Err(Error::InvalidAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,10 +589,7 @@ pub async fn root_user(
|
|||
}
|
||||
}
|
||||
// The password did not verify
|
||||
Err(e) => {
|
||||
debug!("Failed to verify signin credentials for user `{user}` in root: {e}");
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
_ => Err(Error::InvalidAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -645,9 +614,6 @@ pub async fn root_access(
|
|||
// TODO(gguillemas): Remove this once bearer access is no longer experimental.
|
||||
if !*EXPERIMENTAL_BEARER_ACCESS {
|
||||
// Return opaque error to avoid leaking the existence of the feature.
|
||||
debug!(
|
||||
"Error attempting to authenticate with disabled bearer access feature"
|
||||
);
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
// Check if the bearer access method supports issuing tokens.
|
||||
|
@ -663,10 +629,7 @@ pub async fn root_access(
|
|||
let gr = match tx.get_root_access_grant(&ac, &kid).await {
|
||||
Ok(gr) => gr,
|
||||
// Return opaque error to avoid leaking existence of the grant.
|
||||
Err(e) => {
|
||||
debug!("Error retrieving bearer access grant: {e}");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
_ => return Err(Error::InvalidAuth),
|
||||
};
|
||||
// Ensure that the transaction is cancelled.
|
||||
tx.cancel().await?;
|
||||
|
@ -678,7 +641,7 @@ pub async fn root_access(
|
|||
let tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified user from storage.
|
||||
let user = tx.get_root_user(user).await.map_err(|e| {
|
||||
debug!("Error retrieving user for bearer access to root: {e}");
|
||||
trace!("Error while authenticating to root: {e}");
|
||||
// Return opaque error to avoid leaking grant subject existence.
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
|
@ -790,14 +753,10 @@ pub fn verify_grant_bearer(gr: &Arc<AccessGrant>, key: String) -> Result<(), Err
|
|||
(Some(exp), None) => {
|
||||
if exp < &Datetime::default() {
|
||||
// Return opaque error to avoid leaking revocation status.
|
||||
debug!("Bearer access grant `{}` for method `{}` is expired", gr.id, gr.ac);
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
}
|
||||
(_, Some(_)) => {
|
||||
debug!("Bearer access grant `{}` for method `{}` is revoked", gr.id, gr.ac);
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
_ => return Err(Error::InvalidAuth),
|
||||
}
|
||||
// Check if the provided key matches the bearer key in the grant.
|
||||
// We use time-constant comparison to prevent timing attacks.
|
||||
|
@ -806,7 +765,6 @@ pub fn verify_grant_bearer(gr: &Arc<AccessGrant>, key: String) -> Result<(), Err
|
|||
let signin_key_bytes: &[u8] = key.as_bytes();
|
||||
let ok: bool = grant_key_bytes.ct_eq(signin_key_bytes).into();
|
||||
if !ok {
|
||||
debug!("Bearer access grant `{}` for method `{}` is invalid", gr.id, gr.ac);
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -77,7 +77,7 @@ pub async fn db_access(
|
|||
sess.or.clone_from(&session.or);
|
||||
// Compute the value with the params
|
||||
match kvs.evaluate(val, &sess, vars).await {
|
||||
// The signup value succeeded
|
||||
// The signin value succeeded
|
||||
Ok(val) => {
|
||||
match val.record() {
|
||||
// There is a record returned
|
||||
|
@ -113,10 +113,7 @@ pub async fn db_access(
|
|||
// Update rid with result from AUTHENTICATE clause
|
||||
rid = id;
|
||||
}
|
||||
_ => {
|
||||
debug!("Authentication attempt as record user rejected by AUTHENTICATE clause");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
_ => return Err(Error::InvalidAuth),
|
||||
},
|
||||
Err(e) => return match e {
|
||||
Error::Thrown(_) => Err(e),
|
||||
|
@ -156,14 +153,8 @@ pub async fn db_access(
|
|||
}
|
||||
Err(e) => match e {
|
||||
Error::Thrown(_) => Err(e),
|
||||
e => {
|
||||
debug!("Record user signup query failed: {e}");
|
||||
if *INSECURE_FORWARD_ACCESS_ERRORS {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(Error::AccessRecordSignupQueryFailed)
|
||||
}
|
||||
}
|
||||
e if *INSECURE_FORWARD_ACCESS_ERRORS => Err(e),
|
||||
_ => Err(Error::AccessRecordSignupQueryFailed),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,12 +120,7 @@ pub async fn basic(
|
|||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
(None, Some(db)) => {
|
||||
debug!(
|
||||
"Attempted basic authentication in database '{db}' without specifying a namespace"
|
||||
);
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
(None, Some(_)) => Err(Error::InvalidAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,15 +134,15 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
// Check if the auth token can be used
|
||||
if let Some(nbf) = token_data.claims.nbf {
|
||||
if nbf > Utc::now().timestamp() {
|
||||
debug!("Token verification failed due to the 'nbf' claim containing a future time");
|
||||
trace!("The 'nbf' field in the authentication token was invalid");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
}
|
||||
// Check if the auth token has expired
|
||||
if let Some(exp) = token_data.claims.exp {
|
||||
if exp < Utc::now().timestamp() {
|
||||
debug!("Token verification failed due to the 'exp' claim containing a past time");
|
||||
return Err(Error::ExpiredToken);
|
||||
trace!("The 'exp' field in the authentication token was invalid");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
}
|
||||
// Check the token authentication claims
|
||||
|
@ -188,7 +183,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
_ => return Err(Error::AccessMethodMismatch),
|
||||
};
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// AUTHENTICATE clause
|
||||
if let Some(au) = &de.authenticate {
|
||||
// Setup the system session for finding the signin record
|
||||
|
@ -249,7 +244,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
_ => return Err(Error::AccessMethodMismatch),
|
||||
}?;
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// AUTHENTICATE clause
|
||||
if let Some(au) = &de.authenticate {
|
||||
// Setup the system session for executing the clause
|
||||
|
@ -290,7 +285,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
// The clause can make up for the missing "id" claim by resolving other claims to a specific record
|
||||
AccessType::Record(at) => match &de.authenticate {
|
||||
Some(au) => {
|
||||
trace!("Access method `{}` is record access with AUTHENTICATE clause", ac);
|
||||
trace!("Access method `{}` is record access with authenticate clause", ac);
|
||||
let cf = match &at.jwt.verify {
|
||||
JwtAccessVerify::Key(key) => config(key.alg, key.key.as_bytes()),
|
||||
#[cfg(feature = "jwks")]
|
||||
|
@ -306,7 +301,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
}?;
|
||||
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// AUTHENTICATE clause
|
||||
// Setup the system session for finding the signin record
|
||||
let mut sess = Session::editor().with_ns(ns).with_db(db);
|
||||
|
@ -347,7 +342,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
let tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Get the database user
|
||||
let de = tx.get_db_user(ns, db, id).await.map_err(|e| {
|
||||
debug!("Error while authenticating to database `{db}`: {e}");
|
||||
trace!("Error while authenticating to database `{db}`: {e}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled
|
||||
|
@ -355,9 +350,9 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
// Check the algorithm
|
||||
let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
debug!("Authenticated to database `{}` with user `{}` using token", db, id);
|
||||
debug!("Authenticated to database `{}` with user `{}`", db, id);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ns = Some(ns.to_owned());
|
||||
|
@ -402,7 +397,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
_ => return Err(Error::AccessMethodMismatch),
|
||||
}?;
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// AUTHENTICATE clause
|
||||
if let Some(au) = &de.authenticate {
|
||||
// Setup the system session for executing the clause
|
||||
|
@ -425,7 +420,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
.collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
// Log the success
|
||||
debug!("Authenticated to namespace `{}` with access method `{}`", ns, ac);
|
||||
trace!("Authenticated to namespace `{}` with access method `{}`", ns, ac);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ns = Some(ns.to_owned());
|
||||
|
@ -450,7 +445,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
let tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Get the namespace user
|
||||
let de = tx.get_ns_user(ns, id).await.map_err(|e| {
|
||||
debug!("Error while authenticating to namespace `{ns}`: {e}");
|
||||
trace!("Error while authenticating to namespace `{ns}`: {e}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled
|
||||
|
@ -458,9 +453,9 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
// Check the algorithm
|
||||
let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
debug!("Authenticated to namespace `{}` with user `{}` using token", ns, id);
|
||||
trace!("Authenticated to namespace `{}` with user `{}`", ns, id);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ns = Some(ns.to_owned());
|
||||
|
@ -503,7 +498,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
_ => return Err(Error::AccessMethodMismatch),
|
||||
}?;
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// AUTHENTICATE clause
|
||||
if let Some(au) = &de.authenticate {
|
||||
// Setup the system session for executing the clause
|
||||
|
@ -526,7 +521,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
.collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
// Log the success
|
||||
debug!("Authenticated to root with access method `{}`", ac);
|
||||
trace!("Authenticated to root with access method `{}`", ac);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ac = Some(ac.to_owned());
|
||||
|
@ -545,7 +540,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
let tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Get the namespace user
|
||||
let de = tx.get_root_user(id).await.map_err(|e| {
|
||||
debug!("Error while authenticating to root: {e}");
|
||||
trace!("Error while authenticating to root: {e}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled
|
||||
|
@ -553,9 +548,9 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
// Check the algorithm
|
||||
let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
|
||||
// Verify the token
|
||||
verify_token(token, &cf.0, &cf.1)?;
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
debug!("Authenticated to root level with user `{}` using token", id);
|
||||
trace!("Authenticated to root level with user `{}`", id);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.exp = expiration(de.duration.session)?;
|
||||
|
@ -580,7 +575,7 @@ pub async fn verify_root_creds(
|
|||
let tx = ds.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified user from storage
|
||||
let user = tx.get_root_user(user).await.map_err(|e| {
|
||||
debug!("Error retrieving user for authentication to root: {e}");
|
||||
trace!("Error while authenticating to root: {e}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled
|
||||
|
@ -603,7 +598,7 @@ pub async fn verify_ns_creds(
|
|||
let tx = ds.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified user from storage
|
||||
let user = tx.get_ns_user(ns, user).await.map_err(|e| {
|
||||
debug!("Error retrieving user for authentication to namespace `{ns}`: {e}");
|
||||
trace!("Error while authenticating to namespace `{ns}`: {e}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled
|
||||
|
@ -627,7 +622,7 @@ pub async fn verify_db_creds(
|
|||
let tx = ds.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified user from storage
|
||||
let user = tx.get_db_user(ns, db, user).await.map_err(|e| {
|
||||
debug!("Error retrieving user for authentication to database `{ns}/{db}`: {e}");
|
||||
trace!("Error while authenticating to database `{ns}/{db}`: {e}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
// Ensure that the transaction is cancelled
|
||||
|
@ -661,10 +656,7 @@ pub async fn authenticate_record(
|
|||
// If the AUTHENTICATE clause returns a record, authentication continues with that record
|
||||
Some(id) => Ok(id),
|
||||
// If the AUTHENTICATE clause returns anything else, authentication fails generically
|
||||
_ => {
|
||||
debug!("Authentication attempt as record user rejected by AUTHENTICATE clause");
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
_ => Err(Error::InvalidAuth),
|
||||
},
|
||||
Err(e) => match e {
|
||||
// If the AUTHENTICATE clause throws a specific error, authentication fails with that error
|
||||
|
@ -687,10 +679,7 @@ pub async fn authenticate_generic(
|
|||
// If the AUTHENTICATE clause returns nothing, authentication continues
|
||||
Value::None => Ok(()),
|
||||
// If the AUTHENTICATE clause returns anything else, authentication fails generically
|
||||
_ => {
|
||||
debug!("Authentication attempt as system user rejected by AUTHENTICATE clause");
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
_ => Err(Error::InvalidAuth),
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
|
@ -702,22 +691,6 @@ pub async fn authenticate_generic(
|
|||
}
|
||||
}
|
||||
|
||||
fn verify_token(token: &str, key: &DecodingKey, validation: &Validation) -> Result<(), Error> {
|
||||
match decode::<Claims>(token, key, validation) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
// Only transparently return certain token verification errors
|
||||
match err.kind() {
|
||||
jsonwebtoken::errors::ErrorKind::ExpiredSignature => Err(Error::ExpiredToken),
|
||||
_ => {
|
||||
debug!("Error verifying authentication token: {err}");
|
||||
Err(Error::InvalidAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1539,11 +1512,7 @@ mod tests {
|
|||
let mut sess = Session::default();
|
||||
let res = token(&ds, &mut sess, &enc).await;
|
||||
|
||||
match res {
|
||||
Err(Error::ExpiredToken) => {} // ok
|
||||
Err(err) => panic!("Unexpected error signing in with expired token: {:?}", err),
|
||||
res => panic!("Unexpected success signing in with expired token: {:?}", res),
|
||||
}
|
||||
assert!(res.is_err(), "Unexpected success signing in with expired token: {:?}", res);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
@ -116,7 +116,7 @@ impl Filter {
|
|||
|
||||
#[inline]
|
||||
fn uppercase(c: &str) -> FilterResult {
|
||||
Self::check_term(c, c.to_uppercase())
|
||||
Self::check_term(c, c.to_lowercase())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -842,32 +842,4 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_uppercase_tokens() {
|
||||
test_analyzer_tokens(
|
||||
"ANALYZER test TOKENIZERS blank,class FILTERS uppercase",
|
||||
"Ālea IactA!",
|
||||
&[
|
||||
Token::String {
|
||||
chars: (0, 0, 4),
|
||||
bytes: (0, 5),
|
||||
term: "ĀLEA".to_string(),
|
||||
len: 4,
|
||||
},
|
||||
Token::String {
|
||||
chars: (5, 5, 10),
|
||||
bytes: (6, 11),
|
||||
term: "IACTA".to_string(),
|
||||
len: 5,
|
||||
},
|
||||
Token::Ref {
|
||||
chars: (10, 10, 11),
|
||||
bytes: (11, 12),
|
||||
len: 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,15 +112,7 @@ impl QueryPlanner {
|
|||
tree.knn_condition,
|
||||
)
|
||||
.await?;
|
||||
match PlanBuilder::build(
|
||||
tree.root,
|
||||
params,
|
||||
tree.with_indexes,
|
||||
order,
|
||||
tree.all_and_groups,
|
||||
tree.all_and,
|
||||
tree.all_expressions_with_index,
|
||||
)? {
|
||||
match PlanBuilder::build(tree.root, params, tree.with_indexes, order)? {
|
||||
Plan::SingleIndex(exp, io) => {
|
||||
if io.require_distinct() {
|
||||
self.requires_distinct = true;
|
||||
|
|
|
@ -17,27 +17,33 @@ pub(super) struct PlanBuilder {
|
|||
has_indexes: bool,
|
||||
/// List of expressions that are not ranges, backed by an index
|
||||
non_range_indexes: Vec<(Arc<Expression>, IndexOption)>,
|
||||
/// List of indexes allowed in this plan
|
||||
with_indexes: Option<Vec<IndexRef>>,
|
||||
/// List of indexes involved in this plan
|
||||
with_indexes: Vec<IndexRef>,
|
||||
/// Group each possible optimisations local to a SubQuery
|
||||
groups: BTreeMap<GroupRef, Group>, // The order matters because we want the plan to be consistent across repeated queries.
|
||||
/// Does a group contains only AND relations?
|
||||
all_and_groups: HashMap<GroupRef, bool>,
|
||||
/// Does the whole query contains only AND relations?
|
||||
all_and: bool,
|
||||
/// Is every expression backed by an index?
|
||||
all_exp_with_index: bool,
|
||||
}
|
||||
|
||||
impl PlanBuilder {
|
||||
pub(super) fn build(
|
||||
root: Option<Node>,
|
||||
params: &QueryPlannerParams,
|
||||
with_indexes: Option<Vec<IndexRef>>,
|
||||
with_indexes: Vec<IndexRef>,
|
||||
order: Option<IndexOption>,
|
||||
all_and_groups: HashMap<GroupRef, bool>,
|
||||
all_and: bool,
|
||||
all_expressions_with_index: bool,
|
||||
) -> Result<Plan, Error> {
|
||||
let mut b = PlanBuilder {
|
||||
has_indexes: false,
|
||||
non_range_indexes: Default::default(),
|
||||
groups: Default::default(),
|
||||
with_indexes,
|
||||
all_and_groups: Default::default(),
|
||||
all_and: true,
|
||||
all_exp_with_index: true,
|
||||
};
|
||||
|
||||
// If we only count and there are no conditions and no aggregations, then we can only scan keys
|
||||
|
@ -55,7 +61,7 @@ impl PlanBuilder {
|
|||
}
|
||||
|
||||
// If every boolean operator are AND then we can use the single index plan
|
||||
if all_and {
|
||||
if b.all_and {
|
||||
// TODO: This is currently pretty arbitrary
|
||||
// We take the "first" range query if one is available
|
||||
if let Some((_, group)) = b.groups.into_iter().next() {
|
||||
|
@ -73,10 +79,10 @@ impl PlanBuilder {
|
|||
}
|
||||
}
|
||||
// If every expression is backed by an index with can use the MultiIndex plan
|
||||
else if all_expressions_with_index {
|
||||
else if b.all_exp_with_index {
|
||||
let mut ranges = Vec::with_capacity(b.groups.len());
|
||||
for (gr, group) in b.groups {
|
||||
if all_and_groups.get(&gr) == Some(&true) {
|
||||
for (depth, group) in b.groups {
|
||||
if b.all_and_groups.get(&depth) == Some(&true) {
|
||||
group.take_union_ranges(&mut ranges);
|
||||
} else {
|
||||
group.take_intersect_ranges(&mut ranges);
|
||||
|
@ -94,11 +100,9 @@ impl PlanBuilder {
|
|||
|
||||
// Check if we have an explicit list of index we can use
|
||||
fn filter_index_option(&self, io: Option<&IndexOption>) -> Option<IndexOption> {
|
||||
if let Some(io) = io {
|
||||
if let Some(wi) = &self.with_indexes {
|
||||
if !wi.contains(&io.ix_ref()) {
|
||||
return None;
|
||||
}
|
||||
if let Some(io) = &io {
|
||||
if !self.with_indexes.is_empty() && !self.with_indexes.contains(&io.ix_ref()) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
io.cloned()
|
||||
|
@ -113,8 +117,11 @@ impl PlanBuilder {
|
|||
right,
|
||||
exp,
|
||||
} => {
|
||||
let is_bool = self.check_boolean_operator(*group, exp.operator());
|
||||
if let Some(io) = self.filter_index_option(io.as_ref()) {
|
||||
self.add_index_option(*group, exp.clone(), io);
|
||||
} else if self.all_exp_with_index && !is_bool {
|
||||
self.all_exp_with_index = false;
|
||||
}
|
||||
self.eval_node(left)?;
|
||||
self.eval_node(right)?;
|
||||
|
@ -125,6 +132,26 @@ impl PlanBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_boolean_operator(&mut self, gr: GroupRef, op: &Operator) -> bool {
|
||||
match op {
|
||||
Operator::Neg | Operator::Or => {
|
||||
if self.all_and {
|
||||
self.all_and = false;
|
||||
}
|
||||
self.all_and_groups.entry(gr).and_modify(|b| *b = false).or_insert(false);
|
||||
true
|
||||
}
|
||||
Operator::And => {
|
||||
self.all_and_groups.entry(gr).or_insert(true);
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
self.all_and_groups.entry(gr).or_insert(true);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_index_option(&mut self, group_ref: GroupRef, exp: Arc<Expression>, io: IndexOption) {
|
||||
if let IndexOperator::RangePart(_, _) = io.op() {
|
||||
let level = self.groups.entry(group_ref).or_default();
|
||||
|
|
|
@ -20,16 +20,10 @@ use std::sync::Arc;
|
|||
pub(super) struct Tree {
|
||||
pub(super) root: Option<Node>,
|
||||
pub(super) index_map: IndexesMap,
|
||||
pub(super) with_indexes: Option<Vec<IndexRef>>,
|
||||
pub(super) with_indexes: Vec<IndexRef>,
|
||||
pub(super) knn_expressions: KnnExpressions,
|
||||
pub(super) knn_brute_force_expressions: KnnBruteForceExpressions,
|
||||
pub(super) knn_condition: Option<Cond>,
|
||||
/// Is every expression backed by an index?
|
||||
pub(super) all_expressions_with_index: bool,
|
||||
/// Does the whole query contains only AND relations?
|
||||
pub(super) all_and: bool,
|
||||
/// Does a group contains only AND relations?
|
||||
pub(super) all_and_groups: HashMap<GroupRef, bool>,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
|
@ -56,10 +50,6 @@ impl Tree {
|
|||
knn_expressions: b.knn_expressions,
|
||||
knn_brute_force_expressions: b.knn_brute_force_expressions,
|
||||
knn_condition: b.knn_condition,
|
||||
all_expressions_with_index: b.leaf_nodes_count > 0
|
||||
&& b.leaf_nodes_with_index_count == b.leaf_nodes_count,
|
||||
all_and: b.all_and.unwrap_or(true),
|
||||
all_and_groups: b.all_and_groups,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -75,17 +65,13 @@ struct TreeBuilder<'a> {
|
|||
resolved_expressions: HashMap<Arc<Expression>, ResolvedExpression>,
|
||||
resolved_idioms: HashMap<Arc<Idiom>, Node>,
|
||||
index_map: IndexesMap,
|
||||
with_indexes: Option<Vec<IndexRef>>,
|
||||
with_indexes: Vec<IndexRef>,
|
||||
knn_brute_force_expressions: HashMap<Arc<Expression>, KnnBruteForceExpression>,
|
||||
knn_expressions: KnnExpressions,
|
||||
idioms_record_options: HashMap<Arc<Idiom>, RecordOptions>,
|
||||
group_sequence: GroupRef,
|
||||
root: Option<Node>,
|
||||
knn_condition: Option<Cond>,
|
||||
leaf_nodes_count: usize,
|
||||
leaf_nodes_with_index_count: usize,
|
||||
all_and: Option<bool>,
|
||||
all_and_groups: HashMap<GroupRef, bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
|
@ -107,8 +93,8 @@ impl<'a> TreeBuilder<'a> {
|
|||
orders: Option<&'a Orders>,
|
||||
) -> Self {
|
||||
let with_indexes = match with {
|
||||
Some(With::Index(ixs)) => Some(Vec::with_capacity(ixs.len())),
|
||||
_ => None,
|
||||
Some(With::Index(ixs)) => Vec::with_capacity(ixs.len()),
|
||||
_ => vec![],
|
||||
};
|
||||
let first_order = if let Some(o) = orders {
|
||||
o.0.first()
|
||||
|
@ -133,10 +119,6 @@ impl<'a> TreeBuilder<'a> {
|
|||
group_sequence: 0,
|
||||
root: None,
|
||||
knn_condition: None,
|
||||
all_and: None,
|
||||
all_and_groups: Default::default(),
|
||||
leaf_nodes_count: 0,
|
||||
leaf_nodes_with_index_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,10 +188,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
| Value::Param(_)
|
||||
| Value::Null
|
||||
| Value::None
|
||||
| Value::Function(_) => {
|
||||
self.leaf_nodes_count += 1;
|
||||
Ok(Node::Computable)
|
||||
}
|
||||
| Value::Function(_) => Ok(Node::Computable),
|
||||
Value::Array(a) => self.eval_array(stk, a).await,
|
||||
Value::Subquery(s) => self.eval_subquery(stk, s).await,
|
||||
_ => Ok(Node::Unsupported(format!("Unsupported value: {}", v))),
|
||||
|
@ -228,7 +207,6 @@ impl<'a> TreeBuilder<'a> {
|
|||
}
|
||||
|
||||
async fn eval_array(&mut self, stk: &mut Stk, a: &Array) -> Result<Node, Error> {
|
||||
self.leaf_nodes_count += 1;
|
||||
let mut values = Vec::with_capacity(a.len());
|
||||
for v in &a.0 {
|
||||
values.push(stk.run(|stk| v.compute(stk, self.ctx, self.opt, None)).await?);
|
||||
|
@ -242,7 +220,6 @@ impl<'a> TreeBuilder<'a> {
|
|||
group: GroupRef,
|
||||
i: &Idiom,
|
||||
) -> Result<Node, Error> {
|
||||
self.leaf_nodes_count += 1;
|
||||
// Check if the idiom has already been resolved
|
||||
if let Some(node) = self.resolved_idioms.get(i).cloned() {
|
||||
return Ok(node);
|
||||
|
@ -296,11 +273,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
let ixr = self.index_map.definitions.len() as IndexRef;
|
||||
if let Some(With::Index(ixs)) = &self.with {
|
||||
if ixs.contains(&ix.name.0) {
|
||||
if let Some(wi) = &mut self.with_indexes {
|
||||
wi.push(ixr);
|
||||
} else {
|
||||
self.with_indexes = Some(vec![ixr]);
|
||||
}
|
||||
self.with_indexes.push(ixr);
|
||||
}
|
||||
}
|
||||
self.index_map.definitions.push(ix.clone().into());
|
||||
|
@ -370,10 +343,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
match e {
|
||||
Expression::Unary {
|
||||
..
|
||||
} => {
|
||||
self.leaf_nodes_count += 1;
|
||||
Ok(Node::Unsupported("unary expressions not supported".to_string()))
|
||||
}
|
||||
} => Ok(Node::Unsupported("unary expressions not supported".to_string())),
|
||||
Expression::Binary {
|
||||
l,
|
||||
o,
|
||||
|
@ -383,7 +353,6 @@ impl<'a> TreeBuilder<'a> {
|
|||
if let Some(re) = self.resolved_expressions.get(e).cloned() {
|
||||
return Ok(re.into());
|
||||
}
|
||||
self.check_boolean_operator(group, o);
|
||||
let left = stk.run(|stk| self.eval_value(stk, group, l)).await?;
|
||||
let right = stk.run(|stk| self.eval_value(stk, group, r)).await?;
|
||||
// If both values are computable, then we can delegate the computation to the parent
|
||||
|
@ -393,8 +362,9 @@ impl<'a> TreeBuilder<'a> {
|
|||
let exp = Arc::new(e.clone());
|
||||
let left = Arc::new(self.compute(stk, l, left).await?);
|
||||
let right = Arc::new(self.compute(stk, r, right).await?);
|
||||
let io = if let Some((id, local_irs, remote_irs)) = left.is_indexed_field() {
|
||||
self.lookup_index_options(
|
||||
let mut io = None;
|
||||
if let Some((id, local_irs, remote_irs)) = left.is_indexed_field() {
|
||||
io = self.lookup_index_options(
|
||||
o,
|
||||
id,
|
||||
&right,
|
||||
|
@ -402,9 +372,9 @@ impl<'a> TreeBuilder<'a> {
|
|||
IdiomPosition::Left,
|
||||
local_irs,
|
||||
remote_irs,
|
||||
)?
|
||||
)?;
|
||||
} else if let Some((id, local_irs, remote_irs)) = right.is_indexed_field() {
|
||||
self.lookup_index_options(
|
||||
io = self.lookup_index_options(
|
||||
o,
|
||||
id,
|
||||
&left,
|
||||
|
@ -412,16 +382,13 @@ impl<'a> TreeBuilder<'a> {
|
|||
IdiomPosition::Right,
|
||||
local_irs,
|
||||
remote_irs,
|
||||
)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
)?;
|
||||
}
|
||||
if let Some(id) = left.is_field() {
|
||||
self.eval_bruteforce_knn(id, &right, &exp)?;
|
||||
} else if let Some(id) = right.is_field() {
|
||||
self.eval_bruteforce_knn(id, &left, &exp)?;
|
||||
}
|
||||
self.check_leaf_node_with_index(io.as_ref());
|
||||
let re = ResolvedExpression {
|
||||
group,
|
||||
exp: exp.clone(),
|
||||
|
@ -435,37 +402,6 @@ impl<'a> TreeBuilder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_boolean_operator(&mut self, gr: GroupRef, op: &Operator) {
|
||||
match op {
|
||||
Operator::Neg | Operator::Or => {
|
||||
if self.all_and != Some(false) {
|
||||
self.all_and = Some(false);
|
||||
}
|
||||
self.all_and_groups.entry(gr).and_modify(|b| *b = false).or_insert(false);
|
||||
}
|
||||
Operator::And => {
|
||||
if self.all_and.is_none() {
|
||||
self.all_and = Some(true);
|
||||
}
|
||||
self.all_and_groups.entry(gr).or_insert(true);
|
||||
}
|
||||
_ => {
|
||||
self.all_and_groups.entry(gr).or_insert(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_leaf_node_with_index(&mut self, io: Option<&IndexOption>) {
|
||||
if let Some(io) = io {
|
||||
if let Some(wi) = &self.with_indexes {
|
||||
if !wi.contains(&io.ix_ref()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.leaf_nodes_with_index_count += 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn lookup_index_options(
|
||||
&mut self,
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
pub static SURREALCS_CONNECTION_POOL_SIZE: LazyLock<usize> =
|
||||
lazy_env_parse_or_else!("SURREAL_SURREALCS_CONNECTION_POOL_SIZE", usize, |_| num_cpus::get());
|
|
@ -1,7 +1,5 @@
|
|||
#![cfg(feature = "kv-surrealcs")]
|
||||
|
||||
mod cnf;
|
||||
|
||||
use crate::err::Error;
|
||||
use crate::key::debug::Sprintable;
|
||||
use crate::kvs::savepoint::{SaveOperation, SavePointImpl, SavePoints};
|
||||
|
@ -74,7 +72,7 @@ impl Drop for Transaction {
|
|||
impl Datastore {
|
||||
/// Open a new database
|
||||
pub(crate) async fn new(path: &str) -> Result<Datastore, Error> {
|
||||
match create_connection_pool(path, Some(*cnf::SURREALCS_CONNECTION_POOL_SIZE)).await {
|
||||
match create_connection_pool(path, None).await {
|
||||
Ok(_) => Ok(Datastore {}),
|
||||
Err(_) => {
|
||||
Err(Error::Ds("Cannot connect to the `surrealcs` storage engine".to_string()))
|
||||
|
|
|
@ -172,12 +172,9 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
let mut tmp_session = mem::take(self.session_mut());
|
||||
let out: Result<(), RpcError> =
|
||||
crate::iam::verify::token(self.kvs(), &mut tmp_session, &token.0)
|
||||
.await
|
||||
.map_err(Into::into);
|
||||
crate::iam::verify::token(self.kvs(), &mut tmp_session, &token.0).await?;
|
||||
*self.session_mut() = tmp_session;
|
||||
out.map(|_| Value::None.into())
|
||||
Ok(Value::None.into())
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
|
@ -302,6 +299,8 @@ pub trait RpcContext {
|
|||
let Ok((what, data)) = params.needs_two() else {
|
||||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
// Return a single result?
|
||||
let one = what.is_thing_single();
|
||||
// Specify the SQL query string
|
||||
|
||||
let mut res = match what {
|
||||
|
@ -324,7 +323,11 @@ pub trait RpcContext {
|
|||
}
|
||||
};
|
||||
|
||||
let res = res.remove(0).result?;
|
||||
// Extract the first query result
|
||||
let res = match one {
|
||||
true => res.remove(0).result?.first(),
|
||||
false => res.remove(0).result?,
|
||||
};
|
||||
// Return the result to the client
|
||||
Ok(res.into())
|
||||
}
|
||||
|
@ -334,6 +337,8 @@ pub trait RpcContext {
|
|||
return Err(RpcError::InvalidParams);
|
||||
};
|
||||
|
||||
let one = what.is_thing_single();
|
||||
|
||||
let mut res = match what {
|
||||
Value::None | Value::Null => {
|
||||
let sql = "INSERT RELATION $data RETURN AFTER";
|
||||
|
@ -355,7 +360,10 @@ pub trait RpcContext {
|
|||
_ => return Err(RpcError::InvalidParams),
|
||||
};
|
||||
|
||||
let res = res.remove(0).result?;
|
||||
let res = match one {
|
||||
true => res.remove(0).result?.first(),
|
||||
false => res.remove(0).result?,
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::duration::Duration;
|
||||
use crate::sql::strand::Strand;
|
||||
use crate::syn;
|
||||
|
@ -12,7 +11,6 @@ use std::str;
|
|||
use std::str::FromStr;
|
||||
|
||||
use super::escape::quote_str;
|
||||
use super::value::TrySub;
|
||||
|
||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Datetime";
|
||||
|
||||
|
@ -110,13 +108,3 @@ impl ops::Sub<Self> for Datetime {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TrySub for Datetime {
|
||||
type Output = Duration;
|
||||
fn try_sub(self, other: Self) -> Result<Duration, Error> {
|
||||
(self.0 - other.0)
|
||||
.to_std()
|
||||
.map_err(|_| Error::ArithmeticOverflow(format!("{self} - {other}")))
|
||||
.map(Duration::from)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::datetime::Datetime;
|
||||
use crate::sql::statements::info::InfoStructure;
|
||||
use crate::sql::strand::Strand;
|
||||
|
@ -13,8 +12,6 @@ use std::ops::Deref;
|
|||
use std::str::FromStr;
|
||||
use std::time;
|
||||
|
||||
use super::value::{TryAdd, TrySub};
|
||||
|
||||
pub(crate) static SECONDS_PER_YEAR: u64 = 365 * SECONDS_PER_DAY;
|
||||
pub(crate) static SECONDS_PER_WEEK: u64 = 7 * SECONDS_PER_DAY;
|
||||
pub(crate) static SECONDS_PER_DAY: u64 = 24 * SECONDS_PER_HOUR;
|
||||
|
@ -238,16 +235,6 @@ impl ops::Add for Duration {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryAdd for Duration {
|
||||
type Output = Self;
|
||||
fn try_add(self, other: Self) -> Result<Self, Error> {
|
||||
self.0
|
||||
.checked_add(other.0)
|
||||
.ok_or_else(|| Error::ArithmeticOverflow(format!("{self} + {other}")))
|
||||
.map(Duration::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ops::Add<&'b Duration> for &'a Duration {
|
||||
type Output = Duration;
|
||||
fn add(self, other: &'b Duration) -> Duration {
|
||||
|
@ -258,16 +245,6 @@ impl<'a, 'b> ops::Add<&'b Duration> for &'a Duration {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> TryAdd<&'b Duration> for &'a Duration {
|
||||
type Output = Duration;
|
||||
fn try_add(self, other: &'b Duration) -> Result<Duration, Error> {
|
||||
self.0
|
||||
.checked_add(other.0)
|
||||
.ok_or_else(|| Error::ArithmeticOverflow(format!("{self} + {other}")))
|
||||
.map(Duration::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub for Duration {
|
||||
type Output = Self;
|
||||
fn sub(self, other: Self) -> Self {
|
||||
|
@ -278,16 +255,6 @@ impl ops::Sub for Duration {
|
|||
}
|
||||
}
|
||||
|
||||
impl TrySub for Duration {
|
||||
type Output = Self;
|
||||
fn try_sub(self, other: Self) -> Result<Self, Error> {
|
||||
self.0
|
||||
.checked_sub(other.0)
|
||||
.ok_or_else(|| Error::ArithmeticOverflow(format!("{self} - {other}")))
|
||||
.map(Duration::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ops::Sub<&'b Duration> for &'a Duration {
|
||||
type Output = Duration;
|
||||
fn sub(self, other: &'b Duration) -> Duration {
|
||||
|
@ -298,68 +265,26 @@ impl<'a, 'b> ops::Sub<&'b Duration> for &'a Duration {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> TrySub<&'b Duration> for &'a Duration {
|
||||
type Output = Duration;
|
||||
fn try_sub(self, other: &'b Duration) -> Result<Duration, Error> {
|
||||
self.0
|
||||
.checked_sub(other.0)
|
||||
.ok_or_else(|| Error::ArithmeticOverflow(format!("{self} - {other}")))
|
||||
.map(Duration::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Datetime> for Duration {
|
||||
type Output = Datetime;
|
||||
fn add(self, other: Datetime) -> Datetime {
|
||||
match chrono::Duration::from_std(self.0) {
|
||||
Ok(d) => match other.0.checked_add_signed(d) {
|
||||
Some(v) => Datetime::from(v),
|
||||
None => Datetime::default(),
|
||||
},
|
||||
Ok(d) => Datetime::from(other.0 + d),
|
||||
Err(_) => Datetime::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryAdd<Datetime> for Duration {
|
||||
type Output = Datetime;
|
||||
fn try_add(self, other: Datetime) -> Result<Datetime, Error> {
|
||||
match chrono::Duration::from_std(self.0) {
|
||||
Ok(d) => match other.0.checked_add_signed(d) {
|
||||
Some(v) => Ok(Datetime::from(v)),
|
||||
None => Err(Error::ArithmeticOverflow(format!("{self} + {other}"))),
|
||||
},
|
||||
Err(_) => Err(Error::ArithmeticOverflow(format!("{self} + {other}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<Datetime> for Duration {
|
||||
type Output = Datetime;
|
||||
fn sub(self, other: Datetime) -> Datetime {
|
||||
match chrono::Duration::from_std(self.0) {
|
||||
Ok(d) => match other.0.checked_sub_signed(d) {
|
||||
Some(v) => Datetime::from(v),
|
||||
None => Datetime::default(),
|
||||
},
|
||||
Ok(d) => Datetime::from(other.0 - d),
|
||||
Err(_) => Datetime::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TrySub<Datetime> for Duration {
|
||||
type Output = Datetime;
|
||||
fn try_sub(self, other: Datetime) -> Result<Datetime, Error> {
|
||||
match chrono::Duration::from_std(self.0) {
|
||||
Ok(d) => match other.0.checked_sub_signed(d) {
|
||||
Some(v) => Ok(Datetime::from(v)),
|
||||
None => Err(Error::ArithmeticOverflow(format!("{self} - {other}"))),
|
||||
},
|
||||
Err(_) => Err(Error::ArithmeticOverflow(format!("{self} - {other}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum<Self> for Duration {
|
||||
fn sum<I>(iter: I) -> Duration
|
||||
where
|
||||
|
|
|
@ -79,12 +79,15 @@ pub fn escape_ident(s: &str) -> Cow<'_, str> {
|
|||
|
||||
#[inline]
|
||||
pub fn escape_normal<'a>(s: &'a str, l: char, r: char, e: &str) -> Cow<'a, str> {
|
||||
// Is there no need to escape the value?
|
||||
if s.bytes().all(|x| x.is_ascii_alphanumeric() || x == b'_') {
|
||||
return Cow::Borrowed(s);
|
||||
// Loop over each character
|
||||
for x in s.bytes() {
|
||||
// Check if character is allowed
|
||||
if !(x.is_ascii_alphanumeric() || x == b'_') {
|
||||
return Cow::Owned(format!("{l}{}{r}", s.replace(r, e)));
|
||||
}
|
||||
}
|
||||
// Output the value
|
||||
Cow::Owned(format!("{l}{}{r}", s.replace(r, e)))
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
|
||||
pub fn escape_reserved_keyword(s: &str) -> Option<String> {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::escape::quote_plain_str;
|
||||
use revision::revisioned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -7,8 +6,6 @@ use std::ops::Deref;
|
|||
use std::ops::{self};
|
||||
use std::str;
|
||||
|
||||
use super::value::TryAdd;
|
||||
|
||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Strand";
|
||||
|
||||
/// A string that doesn't contain NUL bytes.
|
||||
|
@ -75,18 +72,6 @@ impl ops::Add for Strand {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryAdd for Strand {
|
||||
type Output = Self;
|
||||
fn try_add(mut self, other: Self) -> Result<Self, Error> {
|
||||
if self.0.try_reserve(other.len()).is_ok() {
|
||||
self.0.push_str(other.as_str());
|
||||
Ok(self)
|
||||
} else {
|
||||
Err(Error::ArithmeticOverflow(format!("{self} + {other}")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serde(with = no_nul_bytes) will (de)serialize with no NUL bytes.
|
||||
pub(crate) mod no_nul_bytes {
|
||||
use serde::{
|
||||
|
|
|
@ -64,7 +64,7 @@ mod tests {
|
|||
fn changed_remove() {
|
||||
let old = Value::parse("{ test: true, other: 'test' }");
|
||||
let now = Value::parse("{ test: true }");
|
||||
let res = Value::parse("{ other: NONE }");
|
||||
let res = Value::parse("{ other: NONE }]");
|
||||
assert_eq!(res, old.changed(&now));
|
||||
}
|
||||
|
||||
|
|
|
@ -2981,10 +2981,10 @@ impl TryAdd for Value {
|
|||
fn try_add(self, other: Self) -> Result<Self, Error> {
|
||||
Ok(match (self, other) {
|
||||
(Self::Number(v), Self::Number(w)) => Self::Number(v.try_add(w)?),
|
||||
(Self::Strand(v), Self::Strand(w)) => Self::Strand(v.try_add(w)?),
|
||||
(Self::Datetime(v), Self::Duration(w)) => Self::Datetime(w.try_add(v)?),
|
||||
(Self::Duration(v), Self::Datetime(w)) => Self::Datetime(v.try_add(w)?),
|
||||
(Self::Duration(v), Self::Duration(w)) => Self::Duration(v.try_add(w)?),
|
||||
(Self::Strand(v), Self::Strand(w)) => Self::Strand(v + w),
|
||||
(Self::Datetime(v), Self::Duration(w)) => Self::Datetime(w + v),
|
||||
(Self::Duration(v), Self::Datetime(w)) => Self::Datetime(v + w),
|
||||
(Self::Duration(v), Self::Duration(w)) => Self::Duration(v + w),
|
||||
(v, w) => return Err(Error::TryAdd(v.to_raw_string(), w.to_raw_string())),
|
||||
})
|
||||
}
|
||||
|
@ -2994,7 +2994,7 @@ impl TryAdd for Value {
|
|||
|
||||
pub(crate) trait TrySub<Rhs = Self> {
|
||||
type Output;
|
||||
fn try_sub(self, v: Rhs) -> Result<Self::Output, Error>;
|
||||
fn try_sub(self, v: Self) -> Result<Self::Output, Error>;
|
||||
}
|
||||
|
||||
impl TrySub for Value {
|
||||
|
@ -3002,10 +3002,10 @@ impl TrySub for Value {
|
|||
fn try_sub(self, other: Self) -> Result<Self, Error> {
|
||||
Ok(match (self, other) {
|
||||
(Self::Number(v), Self::Number(w)) => Self::Number(v.try_sub(w)?),
|
||||
(Self::Datetime(v), Self::Datetime(w)) => Self::Duration(v.try_sub(w)?),
|
||||
(Self::Datetime(v), Self::Duration(w)) => Self::Datetime(w.try_sub(v)?),
|
||||
(Self::Duration(v), Self::Datetime(w)) => Self::Datetime(v.try_sub(w)?),
|
||||
(Self::Duration(v), Self::Duration(w)) => Self::Duration(v.try_sub(w)?),
|
||||
(Self::Datetime(v), Self::Datetime(w)) => Self::Duration(v - w),
|
||||
(Self::Datetime(v), Self::Duration(w)) => Self::Datetime(w - v),
|
||||
(Self::Duration(v), Self::Datetime(w)) => Self::Datetime(v - w),
|
||||
(Self::Duration(v), Self::Duration(w)) => Self::Duration(v - w),
|
||||
(v, w) => return Err(Error::TrySub(v.to_raw_string(), w.to_raw_string())),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,120 +12,281 @@ pub struct Location {
|
|||
pub column: usize,
|
||||
}
|
||||
|
||||
/// Safety: b must be a substring of a.
|
||||
unsafe fn str_offset(a: &str, b: &str) -> usize {
|
||||
b.as_ptr().offset_from(a.as_ptr()) as usize
|
||||
}
|
||||
|
||||
impl Location {
|
||||
fn range_of_source_end(source: &str) -> Range<Self> {
|
||||
let (line, column) = source
|
||||
.lines()
|
||||
.enumerate()
|
||||
.last()
|
||||
.map(|(idx, line)| {
|
||||
let idx = idx + 1;
|
||||
let line_idx = line.chars().count().max(1);
|
||||
(idx, line_idx)
|
||||
})
|
||||
.unwrap_or((0, 0));
|
||||
/// Returns the location of the start of substring in the larger input string.
|
||||
///
|
||||
/// Assumption: substr must be a subslice of input.
|
||||
pub fn of_in(substr: &str, input: &str) -> Self {
|
||||
// Bytes of input before substr.
|
||||
let offset = (substr.as_ptr() as usize)
|
||||
.checked_sub(input.as_ptr() as usize)
|
||||
.expect("tried to find location of substring in unrelated string");
|
||||
assert!(offset <= input.len(), "tried to find location of substring in unrelated string");
|
||||
// Bytes of input prior to line being iterated.
|
||||
let mut bytes_prior = 0;
|
||||
for (line_idx, (line, seperator_len)) in LineIterator::new(input).enumerate() {
|
||||
let bytes_so_far = bytes_prior + line.len() + seperator_len.unwrap_or(0) as usize;
|
||||
if bytes_so_far >= offset {
|
||||
// found line.
|
||||
let line_offset = offset - bytes_prior;
|
||||
|
||||
Self {
|
||||
line,
|
||||
column,
|
||||
}..Self {
|
||||
line,
|
||||
column: column + 1,
|
||||
let column = if line_offset > line.len() {
|
||||
// error is inside line terminator.
|
||||
line.chars().count() + 1
|
||||
} else {
|
||||
line[..line_offset].chars().count()
|
||||
};
|
||||
// +1 because line and column are 1 index.
|
||||
return Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
bytes_prior = bytes_so_far;
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
pub fn range_of_span(source: &str, span: Span) -> Range<Self> {
|
||||
if source.len() <= span.offset as usize {
|
||||
return Self::range_of_source_end(source);
|
||||
|
||||
pub fn of_offset(source: &str, offset: usize) -> Self {
|
||||
assert!(offset <= source.len(), "tried to find location of substring in unrelated string");
|
||||
|
||||
if offset == source.len() {
|
||||
// Eof character
|
||||
|
||||
let (last_line, column) = LineIterator::new(source)
|
||||
.enumerate()
|
||||
.last()
|
||||
.map(|(idx, (l, _))| (idx, l.len()))
|
||||
.unwrap_or((0, 0));
|
||||
return Self {
|
||||
line: last_line + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Bytes of input prior to line being iterated.
|
||||
let mut bytes_prior = 0;
|
||||
for (line_idx, (line, seperator_len)) in LineIterator::new(source).enumerate() {
|
||||
let bytes_so_far = bytes_prior + line.len() + seperator_len.unwrap_or(0) as usize;
|
||||
if bytes_so_far >= offset {
|
||||
// found line.
|
||||
let line_offset = offset - bytes_prior;
|
||||
|
||||
let column = if line_offset > line.len() {
|
||||
// error is inside line terminator.
|
||||
line.chars().count() + 1
|
||||
} else {
|
||||
line[..line_offset].chars().count()
|
||||
};
|
||||
// +1 because line and column are 1 index.
|
||||
return Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
bytes_prior = bytes_so_far;
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn of_span_start(source: &str, span: Span) -> Self {
|
||||
// Bytes of input before substr.
|
||||
|
||||
let offset = span.offset as usize;
|
||||
Self::of_offset(source, offset)
|
||||
}
|
||||
|
||||
pub fn of_span_end(source: &str, span: Span) -> Self {
|
||||
// Bytes of input before substr.
|
||||
let offset = span.offset as usize + span.len as usize;
|
||||
Self::of_offset(source, offset)
|
||||
}
|
||||
|
||||
pub fn range_of_span(source: &str, span: Span) -> Range<Self> {
|
||||
if source.len() == span.offset as usize {
|
||||
// EOF span
|
||||
let (line_idx, column) = LineIterator::new(source)
|
||||
.map(|(l, _)| l.len())
|
||||
.enumerate()
|
||||
.last()
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
return Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
}..Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 2,
|
||||
};
|
||||
}
|
||||
|
||||
// Bytes of input before substr.
|
||||
let offset = span.offset as usize;
|
||||
let end = offset + span.len as usize;
|
||||
|
||||
if span.len == 0 && source.len() == span.offset as usize {
|
||||
// EOF span
|
||||
let (last_line, column) = LineIterator::new(source)
|
||||
.enumerate()
|
||||
.last()
|
||||
.map(|(idx, (l, _))| (idx, l.len()))
|
||||
.unwrap_or((0, 0));
|
||||
return Self {
|
||||
line: last_line + 1,
|
||||
column,
|
||||
}..Self {
|
||||
line: last_line + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
|
||||
let mut prev_line = "";
|
||||
let mut lines = source.lines().enumerate().peekable();
|
||||
// Bytes of input prior to line being iteratated.
|
||||
let start_offset = span.offset as usize;
|
||||
let mut bytes_prior = 0;
|
||||
let mut iterator = LineIterator::new(source).enumerate().peekable();
|
||||
let start = loop {
|
||||
let Some((line_idx, line)) = lines.peek().copied() else {
|
||||
// Couldn't find the line, give up and return the last
|
||||
return Self::range_of_source_end(source);
|
||||
let Some((line_idx, (line, seperator_offset))) = iterator.peek() else {
|
||||
panic!("tried to find location of span not belonging to string");
|
||||
};
|
||||
// Safety: line originates from source so it is a substring so calling str_offset is
|
||||
// valid.
|
||||
let line_offset = unsafe { str_offset(source, line) };
|
||||
|
||||
if start_offset < line_offset {
|
||||
// Span is inside the previous line terminator, point to the end of the line.
|
||||
let len = prev_line.chars().count();
|
||||
break Self {
|
||||
line: line_idx,
|
||||
column: len + 1,
|
||||
let bytes_so_far = bytes_prior + line.len() + seperator_offset.unwrap_or(0) as usize;
|
||||
if bytes_so_far > offset {
|
||||
// found line.
|
||||
let line_offset = offset - bytes_prior;
|
||||
let column = if line_offset > line.len() {
|
||||
line.chars().count() + 1
|
||||
} else {
|
||||
line[..line_offset.min(line.len())].chars().count()
|
||||
};
|
||||
// +1 because line and column are 1 index.
|
||||
if bytes_so_far >= end {
|
||||
// end is on the same line, finish immediatly.
|
||||
let line_offset = end - bytes_prior;
|
||||
let end_column = line[..line_offset].chars().count();
|
||||
return Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
}..Self {
|
||||
line: line_idx + 1,
|
||||
column: end_column + 1,
|
||||
};
|
||||
} else {
|
||||
break Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
bytes_prior = bytes_so_far;
|
||||
iterator.next();
|
||||
};
|
||||
|
||||
if (line_offset..(line_offset + line.len())).contains(&start_offset) {
|
||||
let column_offset = start_offset - line_offset;
|
||||
let column = line
|
||||
.char_indices()
|
||||
.enumerate()
|
||||
.find(|(_, (char_idx, _))| *char_idx >= column_offset)
|
||||
.map(|(l, _)| l)
|
||||
.unwrap_or_else(|| {
|
||||
// give up, just point to the end.
|
||||
line.chars().count()
|
||||
});
|
||||
break Self {
|
||||
loop {
|
||||
let Some((line_idx, (line, seperator_offset))) = iterator.next() else {
|
||||
panic!("tried to find location of span not belonging to string");
|
||||
};
|
||||
let bytes_so_far = bytes_prior + line.len() + seperator_offset.unwrap_or(0) as usize;
|
||||
if bytes_so_far >= end {
|
||||
let line_offset = end - bytes_prior;
|
||||
let column = if line_offset > line.len() {
|
||||
line.chars().count() + 1
|
||||
} else {
|
||||
line[..line_offset.min(line.len())].chars().count()
|
||||
};
|
||||
return start..Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
|
||||
lines.next();
|
||||
prev_line = line;
|
||||
};
|
||||
|
||||
let end_offset = span.offset as usize + span.len as usize;
|
||||
let end = loop {
|
||||
let Some((line_idx, line)) = lines.peek().copied() else {
|
||||
// Couldn't find the line, give up and return the last
|
||||
break Self::range_of_source_end(source).end;
|
||||
};
|
||||
// Safety: line originates from source so it is a substring so calling str_offset is
|
||||
// valid.
|
||||
let line_offset = unsafe { str_offset(source, line) };
|
||||
|
||||
if end_offset < line_offset {
|
||||
// Span is inside the previous line terminator, point to the end of the line.
|
||||
let len = prev_line.chars().count();
|
||||
break Self {
|
||||
line: line_idx,
|
||||
column: len + 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (line_offset..(line_offset + line.len())).contains(&end_offset) {
|
||||
let column_offset = end_offset - line_offset;
|
||||
let column = line
|
||||
.char_indices()
|
||||
.enumerate()
|
||||
.find(|(_, (char_idx, _))| *char_idx >= column_offset)
|
||||
.map(|(l, _)| l)
|
||||
.unwrap_or_else(|| {
|
||||
// give up, just point to the end.
|
||||
line.chars().count()
|
||||
});
|
||||
break Self {
|
||||
line: line_idx + 1,
|
||||
column: column + 1,
|
||||
};
|
||||
}
|
||||
|
||||
lines.next();
|
||||
prev_line = line;
|
||||
};
|
||||
|
||||
start..end
|
||||
bytes_prior = bytes_so_far;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LineIterator<'a> {
|
||||
current: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> LineIterator<'a> {
|
||||
pub fn new(s: &'a str) -> Self {
|
||||
LineIterator {
|
||||
current: s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LineIterator<'a> {
|
||||
type Item = (&'a str, Option<u8>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let bytes = self.current.as_bytes();
|
||||
for i in 0..bytes.len() {
|
||||
match bytes[i] {
|
||||
b'\r' => {
|
||||
if let Some(b'\n') = bytes.get(i + 1) {
|
||||
let res = &self.current[..i];
|
||||
self.current = &self.current[i + 2..];
|
||||
return Some((res, Some(2)));
|
||||
}
|
||||
let res = &self.current[..i];
|
||||
self.current = &self.current[i + 1..];
|
||||
return Some((res, Some(1)));
|
||||
}
|
||||
0xb | 0xC | b'\n' => {
|
||||
// vertical tab VT and form feed FF.
|
||||
let res = &self.current[..i];
|
||||
self.current = &self.current[i + 1..];
|
||||
return Some((res, Some(1)));
|
||||
}
|
||||
0xc2 => {
|
||||
// next line NEL
|
||||
if bytes.get(i + 1).copied() != Some(0x85) {
|
||||
continue;
|
||||
}
|
||||
let res = &self.current[..i];
|
||||
self.current = &self.current[i + 2..];
|
||||
return Some((res, Some(2)));
|
||||
}
|
||||
0xe2 => {
|
||||
// line separator and paragraph seperator.
|
||||
if bytes.get(i + 1).copied() != Some(0x80) {
|
||||
continue;
|
||||
}
|
||||
let next_byte = bytes.get(i + 2).copied();
|
||||
if next_byte != Some(0xA8) && next_byte != Some(0xA9) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// vertical tab VT, next line NEL and form feed FF.
|
||||
let res = &self.current[..i];
|
||||
self.current = &self.current[i + 3..];
|
||||
return Some((res, Some(3)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some((std::mem::take(&mut self.current), None))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::LineIterator;
|
||||
|
||||
#[test]
|
||||
fn test_line_iterator() {
|
||||
let lines = "foo\nbar\r\nfoo\rbar\u{000B}foo\u{000C}bar\u{0085}foo\u{2028}bar\u{2029}\n";
|
||||
let mut iterator = LineIterator::new(lines);
|
||||
assert_eq!(iterator.next(), Some(("foo", Some(1))));
|
||||
assert_eq!(iterator.next(), Some(("bar", Some(2))));
|
||||
assert_eq!(iterator.next(), Some(("foo", Some(1))));
|
||||
assert_eq!(iterator.next(), Some(("bar", Some(1))));
|
||||
assert_eq!(iterator.next(), Some(("foo", Some(1))));
|
||||
assert_eq!(iterator.next(), Some(("bar", Some(2))));
|
||||
assert_eq!(iterator.next(), Some(("foo", Some(3))));
|
||||
assert_eq!(iterator.next(), Some(("bar", Some(3))));
|
||||
assert_eq!(iterator.next(), Some(("", Some(1))));
|
||||
assert_eq!(iterator.next(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -222,26 +222,17 @@ impl fmt::Display for Snippet {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{RenderedError, Snippet, Truncation};
|
||||
use crate::syn::{
|
||||
error::{Location, MessageKind},
|
||||
token::Span,
|
||||
};
|
||||
use crate::syn::error::{Location, MessageKind};
|
||||
|
||||
#[test]
|
||||
fn truncate_whitespace() {
|
||||
let source = "\n\n\n\t $ \t";
|
||||
let offset = source.char_indices().find(|(_, c)| *c == '$').unwrap().0;
|
||||
let error = &source[offset..];
|
||||
|
||||
let location = Location::range_of_span(
|
||||
source,
|
||||
Span {
|
||||
offset: offset as u32,
|
||||
len: 1,
|
||||
},
|
||||
);
|
||||
let location = Location::of_in(error, source);
|
||||
|
||||
let snippet =
|
||||
Snippet::from_source_location(source, location.start, None, MessageKind::Error);
|
||||
let snippet = Snippet::from_source_location(source, location, None, MessageKind::Error);
|
||||
assert_eq!(snippet.truncation, Truncation::None);
|
||||
assert_eq!(snippet.offset, 0);
|
||||
assert_eq!(snippet.source.as_str(), "$");
|
||||
|
@ -251,17 +242,11 @@ mod test {
|
|||
fn truncate_start() {
|
||||
let source = " aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $ \t";
|
||||
let offset = source.char_indices().find(|(_, c)| *c == '$').unwrap().0;
|
||||
let error = &source[offset..];
|
||||
|
||||
let location = Location::range_of_span(
|
||||
source,
|
||||
Span {
|
||||
offset: offset as u32,
|
||||
len: 1,
|
||||
},
|
||||
);
|
||||
let location = Location::of_in(error, source);
|
||||
|
||||
let snippet =
|
||||
Snippet::from_source_location(source, location.start, None, MessageKind::Error);
|
||||
let snippet = Snippet::from_source_location(source, location, None, MessageKind::Error);
|
||||
assert_eq!(snippet.truncation, Truncation::Start);
|
||||
assert_eq!(snippet.offset, 10);
|
||||
assert_eq!(snippet.source.as_str(), "aaaaaaaaa $");
|
||||
|
@ -271,17 +256,11 @@ mod test {
|
|||
fn truncate_end() {
|
||||
let source = "\n\n a $ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \t";
|
||||
let offset = source.char_indices().find(|(_, c)| *c == '$').unwrap().0;
|
||||
let error = &source[offset..];
|
||||
|
||||
let location = Location::range_of_span(
|
||||
source,
|
||||
Span {
|
||||
offset: offset as u32,
|
||||
len: 1,
|
||||
},
|
||||
);
|
||||
let location = Location::of_in(error, source);
|
||||
|
||||
let snippet =
|
||||
Snippet::from_source_location(source, location.start, None, MessageKind::Error);
|
||||
let snippet = Snippet::from_source_location(source, location, None, MessageKind::Error);
|
||||
assert_eq!(snippet.truncation, Truncation::End);
|
||||
assert_eq!(snippet.offset, 2);
|
||||
assert_eq!(
|
||||
|
@ -294,17 +273,11 @@ mod test {
|
|||
fn truncate_both() {
|
||||
let source = "\n\n\n\n aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \t";
|
||||
let offset = source.char_indices().find(|(_, c)| *c == '$').unwrap().0;
|
||||
let error = &source[offset..];
|
||||
|
||||
let location = Location::range_of_span(
|
||||
source,
|
||||
Span {
|
||||
offset: offset as u32,
|
||||
len: 1,
|
||||
},
|
||||
);
|
||||
let location = Location::of_in(error, source);
|
||||
|
||||
let snippet =
|
||||
Snippet::from_source_location(source, location.start, None, MessageKind::Error);
|
||||
let snippet = Snippet::from_source_location(source, location, None, MessageKind::Error);
|
||||
assert_eq!(snippet.truncation, Truncation::Both);
|
||||
assert_eq!(snippet.offset, 10);
|
||||
assert_eq!(
|
||||
|
|
|
@ -82,16 +82,9 @@ pub fn numeric(lexer: &mut Lexer, start: Token) -> Result<Numeric, SyntaxError>
|
|||
match start.kind {
|
||||
t!("-") | t!("+") => number(lexer, start).map(Numeric::Number),
|
||||
TokenKind::Digits => match lexer.reader.peek() {
|
||||
Some(b'n' | b'm' | b's' | b'h' | b'y' | b'w') => {
|
||||
Some(b'n' | b'm' | b's' | b'h' | b'y' | b'd' | b'w') => {
|
||||
duration(lexer, start).map(Numeric::Duration)
|
||||
}
|
||||
Some(b'd') => {
|
||||
if lexer.reader.peek1() == Some(b'e') {
|
||||
number(lexer, start).map(Numeric::Number)
|
||||
} else {
|
||||
duration(lexer, start).map(Numeric::Duration)
|
||||
}
|
||||
}
|
||||
Some(x) if !x.is_ascii() => duration(lexer, start).map(Numeric::Duration),
|
||||
_ => number(lexer, start).map(Numeric::Number),
|
||||
},
|
||||
|
|
|
@ -64,7 +64,7 @@ pub fn value(input: &str) -> Result<Value, Error> {
|
|||
.with_query_recursion_limit(*MAX_QUERY_PARSING_DEPTH as usize);
|
||||
let mut stack = Stack::new();
|
||||
stack
|
||||
.enter(|stk| parser.parse_value_field(stk))
|
||||
.enter(|stk| parser.parse_value_table(stk))
|
||||
.finish()
|
||||
.and_then(|e| parser.assert_finished().map(|_| e))
|
||||
.map_err(|e| e.render_on(input))
|
||||
|
@ -211,7 +211,7 @@ pub fn value_legacy_strand(input: &str) -> Result<Value, Error> {
|
|||
let mut stack = Stack::new();
|
||||
parser.allow_legacy_strand(true);
|
||||
stack
|
||||
.enter(|stk| parser.parse_value_field(stk))
|
||||
.enter(|stk| parser.parse_value_table(stk))
|
||||
.finish()
|
||||
.and_then(|e| parser.assert_finished().map(|_| e))
|
||||
.map_err(|e| e.render_on(input))
|
||||
|
|
|
@ -188,7 +188,6 @@ impl Parser<'_> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::sql::{Ident, Part};
|
||||
|
||||
#[test]
|
||||
fn identifiers() {
|
||||
|
@ -207,8 +206,8 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
r,
|
||||
sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Idiom(
|
||||
sql::Idiom(vec![Part::Field(Ident(ident.to_string()))])
|
||||
sql::Query(sql::Statements(vec![sql::Statement::Value(sql::Value::Table(
|
||||
sql::Table(ident.to_string())
|
||||
))]))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ impl<'a> Parser<'a> {
|
|||
last_span: Span::empty(),
|
||||
token_buffer: TokenBuffer::new(),
|
||||
glued_value: GluedValue::None,
|
||||
table_as_field: true,
|
||||
table_as_field: false,
|
||||
legacy_strands: false,
|
||||
flexible_record_id: true,
|
||||
object_recursion: 100,
|
||||
|
@ -180,7 +180,7 @@ impl<'a> Parser<'a> {
|
|||
pub fn reset(&mut self) {
|
||||
self.last_span = Span::empty();
|
||||
self.token_buffer.clear();
|
||||
self.table_as_field = true;
|
||||
self.table_as_field = false;
|
||||
self.lexer.reset();
|
||||
}
|
||||
|
||||
|
@ -319,12 +319,8 @@ impl<'a> Parser<'a> {
|
|||
self.last_span
|
||||
}
|
||||
|
||||
pub fn assert_finished(&mut self) -> ParseResult<()> {
|
||||
let p = self.peek();
|
||||
if self.peek().kind != TokenKind::Eof {
|
||||
bail!("Unexpected token `{}`, expected no more tokens",p.kind, @p.span);
|
||||
}
|
||||
Ok(())
|
||||
pub fn assert_finished(&self) -> ParseResult<()> {
|
||||
self.lexer.assert_finished()
|
||||
}
|
||||
|
||||
/// Eat the next token if it is of the given kind.
|
||||
|
|
|
@ -3,10 +3,9 @@ use std::collections::BTreeMap;
|
|||
use reblessive::Stk;
|
||||
|
||||
use crate::{
|
||||
sql::{Block, Geometry, Object, Strand, Value},
|
||||
sql::{Block, Geometry, Number, Object, Strand, Value},
|
||||
syn::{
|
||||
error::bail,
|
||||
lexer::compound,
|
||||
parser::{enter_object_recursion, mac::expected, ParseResult, Parser},
|
||||
token::{t, Glued, Span, TokenKind},
|
||||
},
|
||||
|
@ -603,8 +602,10 @@ impl Parser<'_> {
|
|||
match token.kind {
|
||||
x if Self::kind_is_keyword_like(x) => {
|
||||
self.pop_peek();
|
||||
let str = self.lexer.span_str(token.span);
|
||||
Ok(str.to_string())
|
||||
let str = self.lexer.reader.span(token.span);
|
||||
// Lexer should ensure that the token is valid utf-8
|
||||
let str = std::str::from_utf8(str).unwrap().to_owned();
|
||||
Ok(str)
|
||||
}
|
||||
TokenKind::Identifier => {
|
||||
self.pop_peek();
|
||||
|
@ -615,16 +616,9 @@ impl Parser<'_> {
|
|||
let str = self.next_token_value::<Strand>()?.0;
|
||||
Ok(str)
|
||||
}
|
||||
TokenKind::Digits => {
|
||||
self.pop_peek();
|
||||
let span = self.lexer.lex_compound(token, compound::number)?.span;
|
||||
let str = self.lexer.span_str(span);
|
||||
Ok(str.to_string())
|
||||
}
|
||||
TokenKind::Glued(Glued::Number) => {
|
||||
self.pop_peek();
|
||||
let str = self.lexer.span_str(token.span);
|
||||
Ok(str.to_string())
|
||||
TokenKind::Digits | TokenKind::Glued(Glued::Number) => {
|
||||
let number = self.next_token_value::<Number>()?.to_string();
|
||||
Ok(number)
|
||||
}
|
||||
_ => unexpected!(self, token, "an object key"),
|
||||
}
|
||||
|
|
|
@ -288,7 +288,6 @@ impl Parser<'_> {
|
|||
t!("RETURN")
|
||||
| t!("SELECT")
|
||||
| t!("CREATE")
|
||||
| t!("INSERT")
|
||||
| t!("UPSERT")
|
||||
| t!("UPDATE")
|
||||
| t!("DELETE")
|
||||
|
|
|
@ -335,12 +335,12 @@ impl Parser<'_> {
|
|||
t!("SIGNUP") => {
|
||||
self.pop_peek();
|
||||
ac.signup =
|
||||
Some(stk.run(|stk| self.parse_value_field(stk)).await?);
|
||||
Some(stk.run(|stk| self.parse_value_table(stk)).await?);
|
||||
}
|
||||
t!("SIGNIN") => {
|
||||
self.pop_peek();
|
||||
ac.signin =
|
||||
Some(stk.run(|stk| self.parse_value_field(stk)).await?);
|
||||
Some(stk.run(|stk| self.parse_value_table(stk)).await?);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("AUTHENTICATE") => {
|
||||
self.pop_peek();
|
||||
res.authenticate = Some(stk.run(|stk| self.parse_value_field(stk)).await?);
|
||||
res.authenticate = Some(stk.run(|stk| self.parse_value_table(stk)).await?);
|
||||
}
|
||||
t!("DURATION") => {
|
||||
self.pop_peek();
|
||||
|
@ -581,11 +581,11 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("SIGNUP") => {
|
||||
self.pop_peek();
|
||||
ac.signup = Some(stk.run(|stk| self.parse_value_field(stk)).await?);
|
||||
ac.signup = Some(stk.run(|stk| self.parse_value_table(stk)).await?);
|
||||
}
|
||||
t!("SIGNIN") => {
|
||||
self.pop_peek();
|
||||
ac.signin = Some(stk.run(|stk| self.parse_value_field(stk)).await?);
|
||||
ac.signin = Some(stk.run(|stk| self.parse_value_table(stk)).await?);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
|
@ -619,7 +619,7 @@ impl Parser<'_> {
|
|||
match self.peek_kind() {
|
||||
t!("VALUE") => {
|
||||
self.pop_peek();
|
||||
res.value = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
||||
res.value = ctx.run(|ctx| self.parse_value_table(ctx)).await?;
|
||||
}
|
||||
t!("COMMENT") => {
|
||||
self.pop_peek();
|
||||
|
@ -758,13 +758,13 @@ impl Parser<'_> {
|
|||
match self.peek_kind() {
|
||||
t!("WHEN") => {
|
||||
self.pop_peek();
|
||||
res.when = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
||||
res.when = ctx.run(|ctx| self.parse_value_table(ctx)).await?;
|
||||
}
|
||||
t!("THEN") => {
|
||||
self.pop_peek();
|
||||
res.then = Values(vec![ctx.run(|ctx| self.parse_value_field(ctx)).await?]);
|
||||
res.then = Values(vec![ctx.run(|ctx| self.parse_value_table(ctx)).await?]);
|
||||
while self.eat(t!(",")) {
|
||||
res.then.0.push(ctx.run(|ctx| self.parse_value_field(ctx)).await?)
|
||||
res.then.0.push(ctx.run(|ctx| self.parse_value_table(ctx)).await?)
|
||||
}
|
||||
}
|
||||
t!("COMMENT") => {
|
||||
|
|
|
@ -129,7 +129,7 @@ impl Parser<'_> {
|
|||
let mut values = Vec::new();
|
||||
let start = expected!(self, t!("(")).span;
|
||||
loop {
|
||||
values.push(self.parse_value_field(ctx).await?);
|
||||
values.push(self.parse_value_table(ctx).await?);
|
||||
|
||||
if !self.eat(t!(",")) {
|
||||
break;
|
||||
|
|
|
@ -214,7 +214,7 @@ impl Parser<'_> {
|
|||
}
|
||||
_ => {
|
||||
// TODO: Provide information about keywords.
|
||||
let value = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
||||
let value = ctx.run(|ctx| self.parse_value_table(ctx)).await?;
|
||||
Ok(Self::refine_stmt_value(value))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,14 +75,6 @@ impl Parser<'_> {
|
|||
}
|
||||
}
|
||||
pub async fn parse_relate_value(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||
let old = self.table_as_field;
|
||||
self.table_as_field = true;
|
||||
let r = self.parse_relate_value_inner(ctx).await;
|
||||
self.table_as_field = old;
|
||||
r
|
||||
}
|
||||
|
||||
async fn parse_relate_value_inner(&mut self, ctx: &mut Stk) -> ParseResult<Value> {
|
||||
match self.peek_kind() {
|
||||
t!("[") => {
|
||||
let start = self.pop_peek().span;
|
||||
|
|
|
@ -5,11 +5,6 @@ fn object_with_negative() {
|
|||
test_parse!(parse_json, r#"{"foo": -1 }"#).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_with_trailing_whitespace() {
|
||||
test_parse!(parse_json, r#"{"foo": -1 }\n"#).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_with_negative() {
|
||||
test_parse!(parse_json, r#"[-1]"#).unwrap();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
sql::{self, Id, Ident, Idiom, Part, Query, Statement, Statements, Thing, Value},
|
||||
sql::{self, Id, Statement, Thing, Value},
|
||||
syn::parser::mac::test_parse,
|
||||
};
|
||||
|
||||
|
@ -9,11 +9,6 @@ mod stmt;
|
|||
mod streaming;
|
||||
mod value;
|
||||
|
||||
#[test]
|
||||
fn parse_large_test_file() {
|
||||
test_parse!(parse_query, include_str!("../../../../test.surql")).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_semicolons() {
|
||||
let res = test_parse!(parse_query, r#";;"#).unwrap();
|
||||
|
@ -88,20 +83,6 @@ fn query_object() {
|
|||
test_parse!(parse_query, src).inspect_err(|e| eprintln!("{}", e.render_on(src))).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ident_is_field() {
|
||||
let src = r#"foo"#;
|
||||
|
||||
let field =
|
||||
test_parse!(parse_query, src).inspect_err(|e| eprintln!("{}", e.render_on(src))).unwrap();
|
||||
assert_eq!(
|
||||
field,
|
||||
Query(Statements(vec![Statement::Value(Value::Idiom(Idiom(vec![Part::Field(Ident(
|
||||
"foo".to_string()
|
||||
))])))]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaped_params_backtick() {
|
||||
test_parse!(
|
||||
|
@ -111,8 +92,3 @@ fn escaped_params_backtick() {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_immediate_insert_subquery() {
|
||||
test_parse!(parse_query, r#"LET $insert = INSERT INTO t (SELECT true FROM 1);"#).unwrap();
|
||||
}
|
||||
|
|
|
@ -41,10 +41,6 @@ use crate::{
|
|||
};
|
||||
use chrono::{offset::TimeZone, NaiveDate, Offset, Utc};
|
||||
|
||||
fn ident_field(name: &str) -> Value {
|
||||
Value::Idiom(Idiom(vec![Part::Field(Ident(name.to_string()))]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn parse_analyze() {
|
||||
let res = test_parse!(parse_stmt, r#"ANALYZE INDEX b on a"#).unwrap();
|
||||
|
@ -209,7 +205,7 @@ fn parse_define_function() {
|
|||
(Ident("b".to_string()), Kind::Array(Box::new(Kind::Bool), Some(3)))
|
||||
],
|
||||
block: Block(vec![Entry::Output(OutputStatement {
|
||||
what: ident_field("a"),
|
||||
what: Value::Table(Table("a".to_string())),
|
||||
fetch: None,
|
||||
})]),
|
||||
comment: Some(Strand("test".to_string())),
|
||||
|
@ -1713,10 +1709,10 @@ fn parse_if() {
|
|||
res,
|
||||
Statement::Ifelse(IfelseStatement {
|
||||
exprs: vec![
|
||||
(ident_field("foo"), ident_field("bar")),
|
||||
(ident_field("faz"), ident_field("baz")),
|
||||
(Value::Table(Table("foo".to_owned())), Value::Table(Table("bar".to_owned()))),
|
||||
(Value::Table(Table("faz".to_owned())), Value::Table(Table("baz".to_owned())))
|
||||
],
|
||||
close: Some(ident_field("baq"))
|
||||
close: Some(Value::Table(Table("baq".to_owned())))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -1730,15 +1726,21 @@ fn parse_if_block() {
|
|||
Statement::Ifelse(IfelseStatement {
|
||||
exprs: vec![
|
||||
(
|
||||
ident_field("foo"),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(ident_field("bar"))]))),
|
||||
Value::Table(Table("foo".to_owned())),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(Value::Table(Table(
|
||||
"bar".to_owned()
|
||||
)),)]))),
|
||||
),
|
||||
(
|
||||
ident_field("faz"),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(ident_field("baz"))]))),
|
||||
Value::Table(Table("faz".to_owned())),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(Value::Table(Table(
|
||||
"baz".to_owned()
|
||||
)),)]))),
|
||||
)
|
||||
],
|
||||
close: Some(Value::Block(Box::new(Block(vec![Entry::Value(ident_field("baq"))])))),
|
||||
close: Some(Value::Block(Box::new(Block(vec![Entry::Value(Value::Table(Table(
|
||||
"baq".to_owned()
|
||||
)))])))),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -2191,8 +2193,10 @@ fn parse_return() {
|
|||
assert_eq!(
|
||||
res,
|
||||
Statement::Output(OutputStatement {
|
||||
what: ident_field("RETRUN"),
|
||||
fetch: Some(Fetchs(vec![Fetch(ident_field("RETURN"))]))
|
||||
what: Value::Table(Table("RETRUN".to_owned())),
|
||||
fetch: Some(Fetchs(vec![Fetch(Value::Idiom(Idiom(vec![Part::Field(
|
||||
Ident("RETURN".to_owned()).to_owned()
|
||||
)])))])),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,10 +31,6 @@ use crate::{
|
|||
use chrono::{offset::TimeZone, NaiveDate, Offset, Utc};
|
||||
use reblessive::Stack;
|
||||
|
||||
fn ident_field(name: &str) -> Value {
|
||||
Value::Idiom(Idiom(vec![Part::Field(Ident(name.to_string()))]))
|
||||
}
|
||||
|
||||
static SOURCE: &str = r#"
|
||||
ANALYZE INDEX b on a;
|
||||
BEGIN;
|
||||
|
@ -196,7 +192,7 @@ fn statements() -> Vec<Statement> {
|
|||
(Ident("b".to_string()), Kind::Array(Box::new(Kind::Bool), Some(3))),
|
||||
],
|
||||
block: Block(vec![Entry::Output(OutputStatement {
|
||||
what: ident_field("a"),
|
||||
what: Value::Table(Table("a".to_string())),
|
||||
fetch: None,
|
||||
})]),
|
||||
comment: Some(Strand("test".to_string())),
|
||||
|
@ -444,23 +440,29 @@ fn statements() -> Vec<Statement> {
|
|||
}),
|
||||
Statement::Ifelse(IfelseStatement {
|
||||
exprs: vec![
|
||||
(ident_field("foo"), ident_field("bar")),
|
||||
(ident_field("faz"), ident_field("baz")),
|
||||
(Value::Table(Table("foo".to_owned())), Value::Table(Table("bar".to_owned()))),
|
||||
(Value::Table(Table("faz".to_owned())), Value::Table(Table("baz".to_owned()))),
|
||||
],
|
||||
close: Some(ident_field("baq")),
|
||||
close: Some(Value::Table(Table("baq".to_owned()))),
|
||||
}),
|
||||
Statement::Ifelse(IfelseStatement {
|
||||
exprs: vec![
|
||||
(
|
||||
ident_field("foo"),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(ident_field("bar"))]))),
|
||||
Value::Table(Table("foo".to_owned())),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(Value::Table(Table(
|
||||
"bar".to_owned(),
|
||||
)))]))),
|
||||
),
|
||||
(
|
||||
ident_field("faz"),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(ident_field("baz"))]))),
|
||||
Value::Table(Table("faz".to_owned())),
|
||||
Value::Block(Box::new(Block(vec![Entry::Value(Value::Table(Table(
|
||||
"baz".to_owned(),
|
||||
)))]))),
|
||||
),
|
||||
],
|
||||
close: Some(Value::Block(Box::new(Block(vec![Entry::Value(ident_field("baq"))])))),
|
||||
close: Some(Value::Block(Box::new(Block(vec![Entry::Value(Value::Table(Table(
|
||||
"baq".to_owned(),
|
||||
)))])))),
|
||||
}),
|
||||
Statement::Info(InfoStatement::Root(false)),
|
||||
Statement::Info(InfoStatement::Ns(false)),
|
||||
|
@ -604,8 +606,10 @@ fn statements() -> Vec<Statement> {
|
|||
id: Value::Uuid(Uuid(uuid::uuid!("e72bee20-f49b-11ec-b939-0242ac120002"))),
|
||||
}),
|
||||
Statement::Output(OutputStatement {
|
||||
what: ident_field("RETRUN"),
|
||||
fetch: Some(Fetchs(vec![Fetch(ident_field("RETURN"))])),
|
||||
what: Value::Table(Table("RETRUN".to_owned())),
|
||||
fetch: Some(Fetchs(vec![Fetch(Value::Idiom(Idiom(vec![Part::Field(
|
||||
Ident("RETURN".to_owned()).to_owned(),
|
||||
)])))])),
|
||||
}),
|
||||
Statement::Relate(RelateStatement {
|
||||
only: true,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use reblessive::Stack;
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
use crate::{
|
||||
sql::{
|
||||
|
@ -13,7 +12,7 @@ use crate::{
|
|||
|
||||
#[test]
|
||||
fn parse_index_expression() {
|
||||
let value = test_parse!(parse_value_field, "a[1 + 1]").unwrap();
|
||||
let value = test_parse!(parse_value_table, "a[1 + 1]").unwrap();
|
||||
let Value::Idiom(x) = value else {
|
||||
panic!("not the right value type");
|
||||
};
|
||||
|
@ -30,7 +29,7 @@ fn parse_index_expression() {
|
|||
|
||||
#[test]
|
||||
fn parse_coordinate() {
|
||||
let coord = test_parse!(parse_value_field, "(1.88, -18.0)").unwrap();
|
||||
let coord = test_parse!(parse_value_table, "(1.88, -18.0)").unwrap();
|
||||
let Value::Geometry(Geometry::Point(x)) = coord else {
|
||||
panic!("not the right value");
|
||||
};
|
||||
|
@ -38,24 +37,14 @@ fn parse_coordinate() {
|
|||
assert_eq!(x.y(), -18.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_numeric_object_key() {
|
||||
let v = test_parse!(parse_value_table, "{ 00: 0 }").unwrap();
|
||||
let Value::Object(object) = v else {
|
||||
panic!("not an object");
|
||||
};
|
||||
assert!(object.len() == 1);
|
||||
assert_eq!(object.get("00").cloned(), Some(Value::Number(Number::Int(0))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_like_operator() {
|
||||
test_parse!(parse_value_field, "a ~ b").unwrap();
|
||||
test_parse!(parse_value_table, "a ~ b").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_range_operator() {
|
||||
test_parse!(parse_value_field, "1..2").unwrap();
|
||||
test_parse!(parse_value_table, "1..2").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -127,7 +116,7 @@ fn parse_large_depth_record_id() {
|
|||
|
||||
#[test]
|
||||
fn parse_recursive_record_string() {
|
||||
let res = test_parse!(parse_value_field, r#" r"a:[r"b:{c: r"d:1"}"]" "#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" r"a:[r"b:{c: r"d:1"}"]" "#).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
Value::Thing(Thing {
|
||||
|
@ -148,7 +137,7 @@ fn parse_recursive_record_string() {
|
|||
|
||||
#[test]
|
||||
fn parse_record_string_2() {
|
||||
let res = test_parse!(parse_value_field, r#" r'a:["foo"]' "#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" r'a:["foo"]' "#).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
Value::Thing(Thing {
|
||||
|
@ -160,88 +149,82 @@ fn parse_record_string_2() {
|
|||
|
||||
#[test]
|
||||
fn parse_i64() {
|
||||
let res = test_parse!(parse_value_field, r#" -9223372036854775808 "#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" -9223372036854775808 "#).unwrap();
|
||||
assert_eq!(res, Value::Number(Number::Int(i64::MIN)));
|
||||
|
||||
let res = test_parse!(parse_value_field, r#" 9223372036854775807 "#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" 9223372036854775807 "#).unwrap();
|
||||
assert_eq!(res, Value::Number(Number::Int(i64::MAX)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_decimal() {
|
||||
let res = test_parse!(parse_value_field, r#" 0dec "#).unwrap();
|
||||
assert_eq!(res, Value::Number(Number::Decimal(Decimal::ZERO)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_lowercase() {
|
||||
let out = test_parse!(parse_value_field, r#" math::pi "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" math::pi "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathPi));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" math::inf "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" math::inf "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathInf));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" math::neg_inf "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" math::neg_inf "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathNegInf));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" time::epoch "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" time::epoch "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::TimeEpoch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_uppercase() {
|
||||
let out = test_parse!(parse_value_field, r#" MATH::PI "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" MATH::PI "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathPi));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" MATH::INF "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" MATH::INF "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathInf));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" MATH::NEG_INF "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" MATH::NEG_INF "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathNegInf));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" TIME::EPOCH "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" TIME::EPOCH "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::TimeEpoch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_mixedcase() {
|
||||
let out = test_parse!(parse_value_field, r#" MaTh::Pi "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" MaTh::Pi "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathPi));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" MaTh::Inf "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" MaTh::Inf "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathInf));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" MaTh::Neg_Inf "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" MaTh::Neg_Inf "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::MathNegInf));
|
||||
|
||||
let out = test_parse!(parse_value_field, r#" TiME::ePoCH "#).unwrap();
|
||||
let out = test_parse!(parse_value_table, r#" TiME::ePoCH "#).unwrap();
|
||||
assert_eq!(out, Value::Constant(Constant::TimeEpoch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scientific_decimal() {
|
||||
let res = test_parse!(parse_value_field, r#" 9.7e-7dec "#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" 9.7e-7dec "#).unwrap();
|
||||
assert!(matches!(res, Value::Number(Number::Decimal(_))));
|
||||
assert_eq!(res.to_string(), "0.00000097dec")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scientific_number() {
|
||||
let res = test_parse!(parse_value_field, r#" 9.7e-5"#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" 9.7e-5"#).unwrap();
|
||||
assert!(matches!(res, Value::Number(Number::Float(_))));
|
||||
assert_eq!(res.to_string(), "0.000097f")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_method() {
|
||||
let res = test_parse!(parse_value_field, r#" 9.7e-5.sin()"#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" 9.7e-5.sin()"#).unwrap();
|
||||
let expected = Value::Idiom(Idiom(vec![
|
||||
Part::Start(Value::Number(Number::Float(9.7e-5))),
|
||||
Part::Method("sin".to_string(), vec![]),
|
||||
]));
|
||||
assert_eq!(res, expected);
|
||||
|
||||
let res = test_parse!(parse_value_field, r#" 1.sin()"#).unwrap();
|
||||
let res = test_parse!(parse_value_table, r#" 1.sin()"#).unwrap();
|
||||
let expected = Value::Idiom(Idiom(vec![
|
||||
Part::Start(Value::Number(Number::Int(1))),
|
||||
Part::Method("sin".to_string(), vec![]),
|
||||
|
@ -251,10 +234,10 @@ fn number_method() {
|
|||
|
||||
#[test]
|
||||
fn datetime_error() {
|
||||
test_parse!(parse_value_field, r#" d"2001-01-01T01:01:01.9999999999" "#).unwrap_err();
|
||||
test_parse!(parse_value_table, r#" d"2001-01-01T01:01:01.9999999999" "#).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_string() {
|
||||
test_parse!(parse_value_field, "").unwrap_err();
|
||||
test_parse!(parse_value_table, "").unwrap_err();
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ impl Parser<'_> {
|
|||
pub(crate) async fn parse_range(&mut self, ctx: &mut Stk) -> ParseResult<Range> {
|
||||
// Check for beginning id
|
||||
let beg = if Self::kind_is_identifier(self.peek_whitespace().kind) {
|
||||
let v = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
|
||||
let v = ctx.run(|ctx| self.parse_value_table(ctx)).await?;
|
||||
|
||||
if self.eat_whitespace(t!(">")) {
|
||||
Bound::Excluded(v)
|
||||
|
@ -142,7 +142,7 @@ impl Parser<'_> {
|
|||
|
||||
// parse ending id.
|
||||
let end = if Self::kind_is_identifier(self.peek_whitespace().kind) {
|
||||
let v = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
|
||||
let v = ctx.run(|ctx| self.parse_value_table(ctx)).await?;
|
||||
if inclusive {
|
||||
Bound::Included(v)
|
||||
} else {
|
||||
|
|
|
@ -69,7 +69,7 @@ impl Parse<Self> for Expression {
|
|||
let mut parser = Parser::new(val.as_bytes());
|
||||
let mut stack = Stack::new();
|
||||
let value = stack
|
||||
.enter(|ctx| parser.parse_value_field(ctx))
|
||||
.enter(|ctx| parser.parse_value_table(ctx))
|
||||
.finish()
|
||||
.map_err(|e| e.render_on(val))
|
||||
.unwrap();
|
||||
|
|
183
flake.nix
183
flake.nix
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
description =
|
||||
"A scalable, distributed, collaborative, document-graph database, for the realtime web";
|
||||
description = "A scalable, distributed, collaborative, document-graph database, for the realtime web";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11-small";
|
||||
|
@ -19,7 +18,8 @@
|
|||
};
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
outputs =
|
||||
inputs:
|
||||
with inputs;
|
||||
|
||||
# Make systems available as variables to prevent typos
|
||||
|
@ -29,12 +29,19 @@
|
|||
# are used to express the output but not themselves paths in the output.
|
||||
let
|
||||
|
||||
nativeSystems = [ aarch64-darwin aarch64-linux x86_64-darwin x86_64-linux ];
|
||||
nativeSystems = [
|
||||
aarch64-darwin
|
||||
aarch64-linux
|
||||
x86_64-darwin
|
||||
x86_64-linux
|
||||
];
|
||||
|
||||
# Build the output set for each default system and map system sets into
|
||||
# attributes, resulting in paths such as:
|
||||
# nix build .#packages.x86_64-linux.<name>
|
||||
in flake-utils.lib.eachSystem nativeSystems (system:
|
||||
in
|
||||
# Build the output set for each default system and map system sets into
|
||||
# attributes, resulting in paths such as:
|
||||
# nix build .#packages.x86_64-linux.<name>
|
||||
flake-utils.lib.eachSystem nativeSystems (
|
||||
system:
|
||||
|
||||
let
|
||||
|
||||
|
@ -47,81 +54,115 @@
|
|||
flake = self;
|
||||
};
|
||||
|
||||
mkRustToolchain = {target, extraComponents ? []}:
|
||||
mkRustToolchain =
|
||||
{
|
||||
target,
|
||||
extraComponents ? [ ],
|
||||
}:
|
||||
with fenix.packages.${system};
|
||||
combine ([
|
||||
stable.rustc
|
||||
stable.cargo
|
||||
targets.${target}.stable.rust-std
|
||||
] ++ extraComponents);
|
||||
combine (
|
||||
[
|
||||
stable.rustc
|
||||
stable.cargo
|
||||
targets.${target}.stable.rust-std
|
||||
]
|
||||
++ extraComponents
|
||||
);
|
||||
|
||||
buildPlatform = pkgs.stdenv.buildPlatform.config;
|
||||
|
||||
# Make platforms available as variables to prevent typos
|
||||
in with util.platforms;
|
||||
in
|
||||
# Make platforms available as variables to prevent typos
|
||||
with util.platforms;
|
||||
|
||||
rec {
|
||||
packages = {
|
||||
# nix build
|
||||
default =
|
||||
packages.${buildPlatform} or packages.x86_64-unknown-linux-gnu;
|
||||
packages =
|
||||
{
|
||||
# nix build
|
||||
default = packages.${buildPlatform} or packages.x86_64-unknown-linux-gnu;
|
||||
|
||||
# nix build .#docker-image
|
||||
docker-image = import ./pkg/nix/drv/docker.nix {
|
||||
inherit util;
|
||||
inherit (pkgs) cacert dockerTools;
|
||||
package = packages.x86_64-unknown-linux-gnu;
|
||||
};
|
||||
|
||||
# nix build .#static-binary
|
||||
static-binary = packages.x86_64-unknown-linux-musl;
|
||||
|
||||
# nix build .#wasm
|
||||
wasm = packages.wasm32-unknown-unknown;
|
||||
|
||||
# nix build .#windows-binary
|
||||
windows-binary = packages.x86_64-pc-windows-gnu;
|
||||
} // (pkgs.lib.attrsets.mapAttrs (target: _:
|
||||
let
|
||||
spec =
|
||||
import ./pkg/nix/spec/${target}.nix { inherit pkgs target util; };
|
||||
in import ./pkg/nix/drv/binary.nix {
|
||||
inherit pkgs util spec crane;
|
||||
rustToolchain = mkRustToolchain { inherit target; };
|
||||
}) util.platforms);
|
||||
|
||||
devShells = {
|
||||
# nix develop
|
||||
default =
|
||||
devShells.${buildPlatform} or devShells.x86_64-unknown-linux-gnu;
|
||||
|
||||
# nix develop .#static-binary
|
||||
static-binary = devShells.x86_64-unknown-linux-musl;
|
||||
|
||||
# nix develop .#wasm
|
||||
wasm = devShells.wasm32-unknown-unknown;
|
||||
|
||||
# nix develop .#windows-binary
|
||||
windows-binary = devShells.x86_64-pc-windows-gnu;
|
||||
} // (pkgs.lib.attrsets.mapAttrs (target: _:
|
||||
let
|
||||
spec = (import ./pkg/nix/spec/${target}.nix) {
|
||||
inherit pkgs target util;
|
||||
# nix build .#docker-image
|
||||
docker-image = import ./pkg/nix/drv/docker.nix {
|
||||
inherit util;
|
||||
inherit (pkgs) cacert dockerTools;
|
||||
package = packages.x86_64-unknown-linux-gnu;
|
||||
};
|
||||
extraComponents = with fenix.packages.${system}; [ targets.${target}.stable.rust-src rust-analyzer targets.${target}.stable.rustfmt ];
|
||||
rustToolchain = mkRustToolchain { inherit target extraComponents; };
|
||||
buildSpec = spec.buildSpec;
|
||||
in pkgs.mkShell (buildSpec // {
|
||||
hardeningDisable = [ "fortify" ];
|
||||
|
||||
depsBuildBuild = buildSpec.depsBuildBuild or [ ]
|
||||
++ [ rustToolchain ] ++ (with pkgs; [ nixfmt cargo-watch wasm-pack pre-commit cargo-make]);
|
||||
# nix build .#static-binary
|
||||
static-binary = packages.x86_64-unknown-linux-musl;
|
||||
|
||||
inherit (util) SURREAL_BUILD_METADATA;
|
||||
})) util.platforms);
|
||||
# nix build .#wasm
|
||||
wasm = packages.wasm32-unknown-unknown;
|
||||
|
||||
# nix build .#windows-binary
|
||||
windows-binary = packages.x86_64-pc-windows-gnu;
|
||||
}
|
||||
// (pkgs.lib.attrsets.mapAttrs (
|
||||
target: _:
|
||||
let
|
||||
spec = import ./pkg/nix/spec/${target}.nix { inherit pkgs target util; };
|
||||
in
|
||||
import ./pkg/nix/drv/binary.nix {
|
||||
inherit
|
||||
pkgs
|
||||
util
|
||||
spec
|
||||
crane
|
||||
;
|
||||
rustToolchain = mkRustToolchain { inherit target; };
|
||||
}
|
||||
) util.platforms);
|
||||
|
||||
devShells =
|
||||
{
|
||||
# nix develop
|
||||
default = devShells.${buildPlatform} or devShells.x86_64-unknown-linux-gnu;
|
||||
|
||||
# nix develop .#static-binary
|
||||
static-binary = devShells.x86_64-unknown-linux-musl;
|
||||
|
||||
# nix develop .#wasm
|
||||
wasm = devShells.wasm32-unknown-unknown;
|
||||
|
||||
# nix develop .#windows-binary
|
||||
windows-binary = devShells.x86_64-pc-windows-gnu;
|
||||
}
|
||||
// (pkgs.lib.attrsets.mapAttrs (
|
||||
target: _:
|
||||
let
|
||||
spec = (import ./pkg/nix/spec/${target}.nix) { inherit pkgs target util; };
|
||||
extraComponents = with fenix.packages.${system}; [
|
||||
targets.${target}.stable.rust-src
|
||||
rust-analyzer
|
||||
targets.${target}.stable.rustfmt
|
||||
];
|
||||
rustToolchain = mkRustToolchain { inherit target extraComponents; };
|
||||
buildSpec = spec.buildSpec;
|
||||
in
|
||||
pkgs.mkShell (
|
||||
buildSpec
|
||||
// {
|
||||
hardeningDisable = [ "fortify" ];
|
||||
|
||||
depsBuildBuild =
|
||||
buildSpec.depsBuildBuild or [ ]
|
||||
#++ [ rustToolchain ]
|
||||
++ (with pkgs; [
|
||||
nixfmt
|
||||
cargo-watch
|
||||
wasm-pack
|
||||
pre-commit
|
||||
cargo-make
|
||||
]);
|
||||
|
||||
inherit (util) SURREAL_BUILD_METADATA;
|
||||
}
|
||||
)
|
||||
) util.platforms);
|
||||
|
||||
# nix run
|
||||
apps.default = flake-utils.lib.mkApp { drv = packages.default; };
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ serial_test = "2.0.0"
|
|||
temp-dir = "0.1.11"
|
||||
test-log = { version = "0.2.13", features = ["trace"] }
|
||||
time = { version = "0.3.36", features = ["serde"] }
|
||||
tokio = { version = "1.40.0", features = ["macros", "sync", "rt-multi-thread"] }
|
||||
tokio = { version = "1", features = ["macros", "sync", "rt-multi-thread"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
ulid = { version = "1.1.0", features = ["serde"] }
|
||||
wiremock = "0.6.0"
|
||||
|
@ -132,7 +132,7 @@ wiremock = "0.6.0"
|
|||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
pharos = "0.5.3"
|
||||
ring = { version = "0.17.7", features = ["wasm32_unknown_unknown_js"] }
|
||||
tokio = { version = "1.40.0", default-features = false, features = [
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"rt",
|
||||
"sync",
|
||||
] }
|
||||
|
@ -144,7 +144,7 @@ wasmtimer = { version = "0.2.0", default-features = false, features = [
|
|||
ws_stream_wasm = "0.7.4"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1.40.0", default-features = false, features = [
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"macros",
|
||||
"io-util",
|
||||
"io-std",
|
||||
|
|
|
@ -54,7 +54,7 @@ mod api_integration {
|
|||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, PartialOrd)]
|
||||
struct RecordBuf {
|
||||
id: RecordId,
|
||||
name: String,
|
||||
|
|
|
@ -502,74 +502,6 @@ async fn create_record_with_id_with_content() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn create_record_with_id_in_content() {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Person {
|
||||
pub id: u32,
|
||||
pub name: &'static str,
|
||||
pub job: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Record {
|
||||
#[allow(dead_code)]
|
||||
pub id: RecordId,
|
||||
}
|
||||
|
||||
let (permit, db) = new_db().await;
|
||||
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
|
||||
drop(permit);
|
||||
|
||||
let record: Option<RecordBuf> = db
|
||||
.create(("user", "john"))
|
||||
.content(RecordBuf {
|
||||
id: RecordId::from_table_key("user", "john"),
|
||||
name: "John Doe".to_owned(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(record.unwrap().id, "user:john".parse::<RecordId>().unwrap());
|
||||
|
||||
let error = db
|
||||
.create::<Option<RecordBuf>>(("user", "john"))
|
||||
.content(RecordBuf {
|
||||
id: RecordId::from_table_key("user", "jane"),
|
||||
name: "John Doe".to_owned(),
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
match error {
|
||||
surrealdb::Error::Db(DbError::IdMismatch {
|
||||
..
|
||||
}) => {}
|
||||
surrealdb::Error::Api(ApiError::Query {
|
||||
..
|
||||
}) => {}
|
||||
error => panic!("unexpected error; {error:?}"),
|
||||
}
|
||||
|
||||
let _: Option<Record> = db
|
||||
.create("person")
|
||||
.content(Person {
|
||||
id: 1010,
|
||||
name: "Max Mustermann",
|
||||
job: "chef",
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _: Option<Record> = db
|
||||
.update(("person", 1010))
|
||||
.content(Person {
|
||||
id: 1010,
|
||||
name: "Max Mustermann",
|
||||
job: "IT Tech",
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn insert_table() {
|
||||
let (permit, db) = new_db().await;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::thread::Builder;
|
||||
|
@ -313,11 +313,7 @@ impl Test {
|
|||
/// Panics if the expected value is not equal to the actual value.
|
||||
/// Compliant with NaN and Constants.
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_value_info<I: Display>(
|
||||
&mut self,
|
||||
val: Value,
|
||||
info: I,
|
||||
) -> Result<&mut Self, Error> {
|
||||
pub fn expect_value(&mut self, val: Value) -> Result<&mut Self, Error> {
|
||||
let tmp = self.next_value()?;
|
||||
// Then check they are indeed the same values
|
||||
//
|
||||
|
@ -328,19 +324,14 @@ impl Test {
|
|||
val
|
||||
};
|
||||
if val.is_nan() {
|
||||
assert!(tmp.is_nan(), "Expected NaN but got {info}: {tmp}");
|
||||
assert!(tmp.is_nan(), "Expected NaN but got: {tmp}");
|
||||
} else {
|
||||
assert_eq!(tmp, val, "{info} {tmp:#}");
|
||||
assert_eq!(tmp, val, "{tmp:#}");
|
||||
}
|
||||
//
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_value(&mut self, val: Value) -> Result<&mut Self, Error> {
|
||||
self.expect_value_info(val, "")
|
||||
}
|
||||
|
||||
/// Expect values in the given slice to be present in the responses, following the same order.
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_values(&mut self, values: &[Value]) -> Result<&mut Self, Error> {
|
||||
|
@ -353,15 +344,7 @@ impl Test {
|
|||
/// Expect the given value to be equals to the next response.
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_val(&mut self, val: &str) -> Result<&mut Self, Error> {
|
||||
self.expect_val_info(val, "")
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_val_info<I: Display>(&mut self, val: &str, info: I) -> Result<&mut Self, Error> {
|
||||
self.expect_value_info(
|
||||
value(val).unwrap_or_else(|_| panic!("INVALID VALUE {info}:\n{val}")),
|
||||
info,
|
||||
)
|
||||
self.expect_value(value(val).unwrap_or_else(|_| panic!("INVALID VALUE:\n{val}")))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -188,7 +188,7 @@ fn three_multi_index_query(with: &str, parallel: &str) -> String {
|
|||
DEFINE INDEX uniq_name ON TABLE person COLUMNS name UNIQUE;
|
||||
DEFINE INDEX idx_genre ON TABLE person COLUMNS genre;
|
||||
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel};
|
||||
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel} EXPLAIN FULL;
|
||||
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel} EXPLAIN FULL;
|
||||
SELECT name FROM person {with} WHERE name = 'Jaime' AND genre = 'm' AND company @@ 'surrealdb' {parallel};
|
||||
SELECT name FROM person {with} WHERE name = 'Jaime' AND genre = 'm' AND company @@ 'surrealdb' {parallel} EXPLAIN FULL;")
|
||||
}
|
||||
|
@ -3063,35 +3063,3 @@ async fn select_composite_standard_index() -> Result<(), Error> {
|
|||
async fn select_composite_unique_index() -> Result<(), Error> {
|
||||
select_composite_index(true).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn select_where_index_boolean_behaviour() -> Result<(), Error> {
|
||||
let sql = r"
|
||||
DEFINE INDEX flagIndex ON TABLE test COLUMNS flag;
|
||||
CREATE test:t CONTENT { flag:true };
|
||||
CREATE test:f CONTENT { flag:false };
|
||||
SELECT * FROM test;
|
||||
SELECT * FROM test WITH NOINDEX WHERE (true OR flag=true);
|
||||
SELECT * FROM test WITH NOINDEX WHERE (true OR flag==true);
|
||||
SELECT * FROM test WHERE (true OR flag=true);
|
||||
SELECT * FROM test WHERE (true OR flag==true);";
|
||||
let mut t = Test::new(sql).await?;
|
||||
t.expect_size(8)?;
|
||||
t.skip_ok(3)?;
|
||||
for i in 0..5 {
|
||||
t.expect_val_info(
|
||||
"[
|
||||
{
|
||||
flag: false,
|
||||
id: test:f
|
||||
},
|
||||
{
|
||||
flag: true,
|
||||
id: test:t
|
||||
}
|
||||
]",
|
||||
i,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -348,44 +348,13 @@ async fn insert() -> Result<(), Box<dyn std::error::Error>> {
|
|||
assert_eq!(res.len(), 1, "result: {res:?}");
|
||||
assert_eq!(res[0]["name"], "foo", "result: {res:?}");
|
||||
assert_eq!(res[0]["value"], "bar", "result: {res:?}");
|
||||
// Send INSERT command trying to create multiple records
|
||||
let res = socket
|
||||
.send_request(
|
||||
"insert",
|
||||
json!([
|
||||
"tester",
|
||||
[
|
||||
{
|
||||
"name": "foo",
|
||||
"value": "bar",
|
||||
},
|
||||
{
|
||||
"name": "foo",
|
||||
"value": "bar",
|
||||
}
|
||||
]
|
||||
]),
|
||||
)
|
||||
.await?;
|
||||
assert!(res.is_object(), "result: {res:?}");
|
||||
assert!(res["result"].is_array(), "result: {res:?}");
|
||||
let res = res["result"].as_array().unwrap();
|
||||
assert_eq!(res.len(), 2, "result: {res:?}");
|
||||
assert_eq!(res[0]["name"], "foo", "result: {res:?}");
|
||||
assert_eq!(res[0]["value"], "bar", "result: {res:?}");
|
||||
assert_eq!(res[1]["name"], "foo", "result: {res:?}");
|
||||
assert_eq!(res[1]["value"], "bar", "result: {res:?}");
|
||||
// Verify the data was inserted and can be queried
|
||||
let res = socket.send_message_query("SELECT * FROM tester").await?;
|
||||
assert!(res[0]["result"].is_array(), "result: {res:?}");
|
||||
let res = res[0]["result"].as_array().unwrap();
|
||||
assert_eq!(res.len(), 3, "result: {res:?}");
|
||||
assert_eq!(res.len(), 1, "result: {res:?}");
|
||||
assert_eq!(res[0]["name"], "foo", "result: {res:?}");
|
||||
assert_eq!(res[0]["value"], "bar", "result: {res:?}");
|
||||
assert_eq!(res[1]["name"], "foo", "result: {res:?}");
|
||||
assert_eq!(res[1]["value"], "bar", "result: {res:?}");
|
||||
assert_eq!(res[2]["name"], "foo", "result: {res:?}");
|
||||
assert_eq!(res[2]["value"], "bar", "result: {res:?}");
|
||||
// Test passed
|
||||
server.finish().unwrap();
|
||||
Ok(())
|
||||
|
@ -1505,25 +1474,6 @@ async fn session_reauthentication_expired() {
|
|||
server.finish().unwrap();
|
||||
}
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn session_failed_reauthentication() {
|
||||
// Setup database server without authentication
|
||||
let (addr, mut server) = common::start_server_without_auth().await.unwrap();
|
||||
// Connect to WebSocket
|
||||
let mut socket = Socket::connect(&addr, SERVER, FORMAT).await.unwrap();
|
||||
// Specify a namespace and database to use
|
||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||
// Check that we have are have a database and namespace selected
|
||||
socket.send_message_query("INFO FOR DB").await.unwrap();
|
||||
// Authenticate using an invalid token
|
||||
socket.send_request("authenticate", json!(["invalid",])).await.unwrap();
|
||||
// Check to see if we still have a namespace and database selected
|
||||
let res = socket.send_message_query("INFO FOR DB").await.unwrap();
|
||||
assert_eq!(res[0]["status"], "OK", "result: {res:?}");
|
||||
// Test passed
|
||||
server.finish().unwrap();
|
||||
}
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn session_use_change_database() {
|
||||
// Setup database server
|
||||
|
|
Loading…
Reference in a new issue