use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GqlStatus {
code: [u8; 5],
}
impl GqlStatus {
pub const SUCCESS: Self = Self::from_bytes(*b"00000");
pub const SUCCESS_OMITTED_RESULT: Self = Self::from_bytes(*b"00001");
pub const WARNING: Self = Self::from_bytes(*b"01000");
pub const WARNING_STRING_TRUNCATION: Self = Self::from_bytes(*b"01004");
pub const WARNING_GRAPH_NOT_EXIST: Self = Self::from_bytes(*b"01G03");
pub const WARNING_GRAPH_TYPE_NOT_EXIST: Self = Self::from_bytes(*b"01G04");
pub const WARNING_NULL_ELIMINATED: Self = Self::from_bytes(*b"01G11");
pub const NO_DATA: Self = Self::from_bytes(*b"02000");
pub const DATA_EXCEPTION: Self = Self::from_bytes(*b"22000");
pub const DATA_STRING_TRUNCATION: Self = Self::from_bytes(*b"22001");
pub const DATA_NUMERIC_OUT_OF_RANGE: Self = Self::from_bytes(*b"22003");
pub const DATA_NULL_NOT_ALLOWED: Self = Self::from_bytes(*b"22004");
pub const DATA_DIVISION_BY_ZERO: Self = Self::from_bytes(*b"22012");
pub const DATA_NEGATIVE_LIMIT: Self = Self::from_bytes(*b"22G02");
pub const DATA_INVALID_VALUE_TYPE: Self = Self::from_bytes(*b"22G03");
pub const DATA_VALUES_NOT_COMPARABLE: Self = Self::from_bytes(*b"22G04");
pub const INVALID_TX_STATE: Self = Self::from_bytes(*b"25000");
pub const INVALID_TX_ACTIVE: Self = Self::from_bytes(*b"25G01");
pub const INVALID_TX_READ_ONLY: Self = Self::from_bytes(*b"25G03");
pub const INVALID_TX_TERMINATION: Self = Self::from_bytes(*b"2D000");
pub const TX_ROLLBACK: Self = Self::from_bytes(*b"40000");
pub const TX_COMPLETION_UNKNOWN: Self = Self::from_bytes(*b"40003");
pub const SYNTAX_ERROR: Self = Self::from_bytes(*b"42000");
pub const SYNTAX_INVALID: Self = Self::from_bytes(*b"42001");
pub const SYNTAX_INVALID_REFERENCE: Self = Self::from_bytes(*b"42002");
pub const DEPENDENT_OBJECT_ERROR: Self = Self::from_bytes(*b"G1000");
pub const GRAPH_TYPE_VIOLATION: Self = Self::from_bytes(*b"G2000");
#[must_use]
const fn from_bytes(bytes: [u8; 5]) -> Self {
Self { code: bytes }
}
#[must_use]
pub fn from_str(s: &str) -> Option<Self> {
let bytes = s.as_bytes();
if bytes.len() != 5 {
return None;
}
if !bytes.iter().all(|b| b.is_ascii_alphanumeric()) {
return None;
}
Some(Self {
code: [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]],
})
}
#[must_use]
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.code).unwrap_or("?????")
}
#[must_use]
pub fn class_code(&self) -> &str {
&self.as_str()[..2]
}
#[must_use]
pub fn subclass_code(&self) -> &str {
&self.as_str()[2..]
}
#[must_use]
pub fn is_success(&self) -> bool {
self.code[0] == b'0' && self.code[1] == b'0'
}
#[must_use]
pub fn is_warning(&self) -> bool {
self.code[0] == b'0' && self.code[1] == b'1'
}
#[must_use]
pub fn is_no_data(&self) -> bool {
self.code[0] == b'0' && self.code[1] == b'2'
}
#[must_use]
pub fn is_exception(&self) -> bool {
!self.is_success() && !self.is_warning() && !self.is_no_data()
}
}
impl fmt::Display for GqlStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&super::error::Error> for GqlStatus {
fn from(err: &super::error::Error) -> Self {
use super::error::{Error, QueryErrorKind, TransactionError};
match err {
Error::Query(q) => match q.kind {
QueryErrorKind::Lexer | QueryErrorKind::Syntax => GqlStatus::SYNTAX_INVALID,
QueryErrorKind::Semantic => GqlStatus::SYNTAX_INVALID_REFERENCE,
QueryErrorKind::Optimization => GqlStatus::SYNTAX_ERROR,
QueryErrorKind::Execution => GqlStatus::DATA_EXCEPTION,
QueryErrorKind::Timeout => GqlStatus::DATA_EXCEPTION,
},
Error::Transaction(t) => match t {
TransactionError::ReadOnly => GqlStatus::INVALID_TX_READ_ONLY,
TransactionError::InvalidState(_) => GqlStatus::INVALID_TX_STATE,
TransactionError::Aborted
| TransactionError::Conflict
| TransactionError::WriteConflict(_) => GqlStatus::TX_ROLLBACK,
TransactionError::SerializationFailure(_) => GqlStatus::TX_ROLLBACK,
TransactionError::Deadlock => GqlStatus::TX_ROLLBACK,
TransactionError::Timeout => GqlStatus::INVALID_TX_STATE,
},
Error::TypeMismatch { .. } => GqlStatus::DATA_INVALID_VALUE_TYPE,
Error::InvalidValue(_) => GqlStatus::DATA_EXCEPTION,
Error::NodeNotFound(_) | Error::EdgeNotFound(_) => GqlStatus::NO_DATA,
Error::PropertyNotFound(_) | Error::LabelNotFound(_) => {
GqlStatus::SYNTAX_INVALID_REFERENCE
}
Error::Storage(_) => GqlStatus::DATA_EXCEPTION,
Error::Serialization(_) => GqlStatus::DATA_EXCEPTION,
Error::Io(_) => GqlStatus::DATA_EXCEPTION,
Error::Internal(_) => GqlStatus::DATA_EXCEPTION,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiagnosticRecord {
pub operation: String,
pub operation_code: i32,
pub current_schema: Option<String>,
pub invalid_reference: Option<String>,
}
impl DiagnosticRecord {
#[must_use]
pub fn for_query(operation: impl Into<String>, operation_code: i32) -> Self {
Self {
operation: operation.into(),
operation_code,
current_schema: None,
invalid_reference: None,
}
}
}
pub mod operation_codes {
pub const SESSION_SET_SCHEMA: i32 = 1;
pub const SESSION_SET_GRAPH: i32 = 2;
pub const SESSION_SET_TIME_ZONE: i32 = 3;
pub const SESSION_RESET: i32 = 7;
pub const SESSION_CLOSE: i32 = 8;
pub const START_TRANSACTION: i32 = 50;
pub const ROLLBACK: i32 = 51;
pub const COMMIT: i32 = 52;
pub const CREATE_SCHEMA: i32 = 100;
pub const DROP_SCHEMA: i32 = 101;
pub const CREATE_GRAPH: i32 = 200;
pub const DROP_GRAPH: i32 = 201;
pub const CREATE_GRAPH_TYPE: i32 = 300;
pub const DROP_GRAPH_TYPE: i32 = 301;
pub const INSERT: i32 = 500;
pub const SET: i32 = 501;
pub const REMOVE: i32 = 502;
pub const DELETE: i32 = 503;
pub const MATCH: i32 = 600;
pub const FILTER: i32 = 601;
pub const LET: i32 = 602;
pub const FOR: i32 = 603;
pub const ORDER_BY_AND_PAGE: i32 = 604;
pub const RETURN: i32 = 605;
pub const SELECT: i32 = 606;
pub const CALL_PROCEDURE: i32 = 800;
pub const UNRECOGNIZED: i32 = 0;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gqlstatus_constants() {
assert_eq!(GqlStatus::SUCCESS.as_str(), "00000");
assert_eq!(GqlStatus::NO_DATA.as_str(), "02000");
assert_eq!(GqlStatus::SYNTAX_INVALID.as_str(), "42001");
assert_eq!(GqlStatus::TX_ROLLBACK.as_str(), "40000");
}
#[test]
fn test_gqlstatus_classification() {
assert!(GqlStatus::SUCCESS.is_success());
assert!(!GqlStatus::SUCCESS.is_warning());
assert!(!GqlStatus::SUCCESS.is_exception());
assert!(GqlStatus::WARNING.is_warning());
assert!(!GqlStatus::WARNING.is_success());
assert!(!GqlStatus::WARNING.is_exception());
assert!(GqlStatus::NO_DATA.is_no_data());
assert!(!GqlStatus::NO_DATA.is_exception());
assert!(GqlStatus::SYNTAX_ERROR.is_exception());
assert!(GqlStatus::DATA_EXCEPTION.is_exception());
assert!(GqlStatus::TX_ROLLBACK.is_exception());
}
#[test]
fn test_gqlstatus_class_subclass() {
assert_eq!(GqlStatus::SUCCESS.class_code(), "00");
assert_eq!(GqlStatus::SUCCESS.subclass_code(), "000");
assert_eq!(GqlStatus::SYNTAX_INVALID.class_code(), "42");
assert_eq!(GqlStatus::SYNTAX_INVALID.subclass_code(), "001");
assert_eq!(GqlStatus::DATA_DIVISION_BY_ZERO.class_code(), "22");
assert_eq!(GqlStatus::DATA_DIVISION_BY_ZERO.subclass_code(), "012");
}
#[test]
fn test_gqlstatus_from_str() {
assert_eq!(GqlStatus::from_str("00000"), Some(GqlStatus::SUCCESS));
assert_eq!(GqlStatus::from_str("0000"), None); assert_eq!(GqlStatus::from_str("000000"), None); assert_eq!(GqlStatus::from_str("00 00"), None); }
#[test]
fn test_gqlstatus_display() {
assert_eq!(format!("{}", GqlStatus::SUCCESS), "00000");
assert_eq!(format!("{}", GqlStatus::SYNTAX_INVALID), "42001");
}
#[test]
fn test_error_to_gqlstatus() {
use super::super::error::{Error, QueryError, QueryErrorKind, TransactionError};
let syntax_err = Error::Query(QueryError::new(QueryErrorKind::Syntax, "bad syntax"));
assert_eq!(GqlStatus::from(&syntax_err), GqlStatus::SYNTAX_INVALID);
let semantic_err = Error::Query(QueryError::new(QueryErrorKind::Semantic, "unknown label"));
assert_eq!(
GqlStatus::from(&semantic_err),
GqlStatus::SYNTAX_INVALID_REFERENCE
);
let tx_err = Error::Transaction(TransactionError::ReadOnly);
assert_eq!(GqlStatus::from(&tx_err), GqlStatus::INVALID_TX_READ_ONLY);
let conflict = Error::Transaction(TransactionError::Conflict);
assert_eq!(GqlStatus::from(&conflict), GqlStatus::TX_ROLLBACK);
let type_err = Error::TypeMismatch {
expected: "INT64".into(),
found: "STRING".into(),
};
assert_eq!(
GqlStatus::from(&type_err),
GqlStatus::DATA_INVALID_VALUE_TYPE
);
}
#[test]
fn test_diagnostic_record() {
let record = DiagnosticRecord::for_query("MATCH STATEMENT", operation_codes::MATCH);
assert_eq!(record.operation, "MATCH STATEMENT");
assert_eq!(record.operation_code, 600);
assert!(record.current_schema.is_none());
assert!(record.invalid_reference.is_none());
}
}