use super::*;
use crate::parser::ast::vector::{VectorDistance, VectorIndexConfig, VectorMatchCondition};
use linkrs_core::types::expr::contextual::ContextualExpression;
fn ctx_expr() -> ContextualExpression {
let expr_context = Arc::new(ExpressionAnalysisContext::new());
let expr = linkrs_core::types::expr::Expression::Variable("x".to_string());
let expr_meta = linkrs_core::types::expr::ExpressionMeta::new(expr);
let expr_id = expr_context.register_expression(expr_meta);
ContextualExpression::new(expr_id, expr_context)
}
#[test]
fn test_stmt_category_exhaustive() {
use StmtCategory::*;
let span = Span::default();
let from = FromClause {
span,
vertices: vec![],
};
let yield_clause = YieldClause {
span,
distinct: false,
items: vec![],
where_clause: None,
order_by: None,
limit: None,
skip: None,
sample: None,
};
let nested = move || {
Stmt::Use(UseStmt {
span,
space: "s".to_string(),
})
};
let vector_query = VectorQueryExpr {
span,
query_type: crate::parser::ast::vector::VectorQueryType::Vector,
query_data: "[0.1, 0.2, 0.3]".to_string(),
};
let cases: Vec<(Stmt, StmtCategory)> = vec![
(Stmt::Query(QueryStmt::new(vec![], span)), Query),
(
Stmt::Pipe(PipeStmt {
span,
left: Box::new(nested()),
right: Box::new(nested()),
}),
Query,
),
(
Stmt::SetOperation(SetOperationStmt {
span,
op_type: SetOperationType::Union,
left: Box::new(nested()),
right: Box::new(nested()),
}),
Query,
),
(
Stmt::Assignment(AssignmentStmt {
span,
variable: "v".to_string(),
statement: Box::new(nested()),
}),
Query,
),
(
Stmt::Match(MatchStmt {
span,
patterns: vec![],
where_clause: None,
return_clause: None,
order_by: None,
limit: None,
skip: None,
join_hint: None,
optional: false,
delete_clause: None,
}),
Dql,
),
(
Stmt::Go(GoStmt {
span,
steps: Steps::Fixed(1),
from: from.clone(),
over: None,
where_clause: None,
yield_clause: None,
}),
Dql,
),
(
Stmt::Fetch(FetchStmt {
span,
target: FetchTarget::Vertices {
tag_name: None,
ids: vec![],
properties: None,
},
yield_clause: None,
}),
Dql,
),
(
Stmt::Lookup(LookupStmt {
span,
target: LookupTarget::Tag("Person".to_string()),
where_clause: None,
yield_clause: None,
}),
Dql,
),
(
Stmt::Subgraph(SubgraphStmt {
span,
steps: Steps::Fixed(1),
from: from.clone(),
over: None,
where_clause: None,
yield_clause: None,
with_prop: false,
}),
Dql,
),
(
Stmt::FindPath(FindPathStmt {
span,
from,
to: ctx_expr(),
over: None,
where_clause: None,
shortest: true,
max_steps: None,
limit: None,
skip: None,
yield_clause: None,
weight_expression: None,
heuristic_expression: None,
with_loop: false,
with_trail: false,
with_walk: false,
}),
Dql,
),
(
Stmt::Unwind(UnwindStmt {
span,
expression: ctx_expr(),
variable: "v".to_string(),
return_clause: None,
order_by: None,
limit: None,
skip: None,
}),
Dql,
),
(
Stmt::Return(ReturnStmt {
span,
items: vec![],
distinct: false,
order_by: None,
skip: None,
limit: None,
}),
Dql,
),
(
Stmt::With(WithStmt {
span,
items: vec![],
where_clause: None,
distinct: false,
order_by: None,
skip: None,
limit: None,
recursive: false,
ctes: vec![],
}),
Dql,
),
(
Stmt::Yield(YieldStmt {
span,
items: vec![],
where_clause: None,
distinct: false,
order_by: None,
skip: None,
limit: None,
sample: None,
}),
Dql,
),
(
Stmt::Filter(FilterStmt {
span,
expression: ctx_expr(),
}),
Dql,
),
(
Stmt::Collect(CollectStmt {
span,
items: vec![],
}),
Dql,
),
(
Stmt::GroupBy(GroupByStmt {
span,
group_items: vec![],
grouping_type: GroupingType::Standard,
yield_clause,
having_clause: None,
}),
Dql,
),
(
Stmt::AssignVariable(AssignVariableStmt {
span,
name: "x".to_string(),
expression: ctx_expr(),
}),
Dql,
),
(
Stmt::Insert(InsertStmt {
span,
target: InsertTarget::Vertices {
tag: TagInsertSpec {
tag_name: String::new(),
prop_names: vec![],
is_default_props: false,
},
values: vec![],
},
if_not_exists: false,
}),
Dml,
),
(
Stmt::Merge(MergeStmt {
span,
pattern: Pattern::Node(NodePattern::new(None, vec![], None, vec![], span)),
on_create: None,
on_match: None,
}),
Dml,
),
(
Stmt::Update(UpdateStmt {
span,
target: UpdateTarget::Vertex(ctx_expr()),
set_clause: SetClause {
span,
assignments: vec![],
},
where_clause: None,
is_upsert: false,
yield_clause: None,
}),
Dml,
),
(
Stmt::Delete(DeleteStmt {
span,
target: DeleteTarget::Vertices {
tag: String::new(),
vids: vec![],
},
with_edge: false,
detach: false,
}),
Dml,
),
(
Stmt::Set(SetStmt {
span,
assignments: vec![],
}),
Dml,
),
(
Stmt::Remove(RemoveStmt {
span,
items: vec![],
}),
Dml,
),
(
Stmt::Create(CreateStmt {
span,
target: CreateTarget::Tag {
name: "tag".to_string(),
properties: vec![],
ttl_duration: None,
ttl_col: None,
},
if_not_exists: false,
}),
Ddl,
),
(
Stmt::Drop(DropStmt {
span,
target: DropTarget::Space("s".to_string()),
if_exists: false,
}),
Ddl,
),
(
Stmt::Alter(AlterStmt {
span,
target: AlterTarget::Tag {
tag_name: "person".to_string(),
additions: vec![],
deletions: vec![],
changes: vec![],
new_comment: None,
},
}),
Ddl,
),
(
Stmt::Desc(DescStmt {
span,
target: DescTarget::Space("s".to_string()),
}),
Ddl,
),
(
Stmt::ShowCreate(ShowCreateStmt {
span,
target: ShowCreateTarget::Space("s".to_string()),
}),
Ddl,
),
(
Stmt::CreateFulltextIndex(CreateFulltextIndex::new(
span,
"idx".to_string(),
"s".to_string(),
vec![],
linkrs_core::types::FulltextEngineType::Bm25,
)),
Ddl,
),
(
Stmt::DropFulltextIndex(DropFulltextIndex {
span,
index_name: "idx".to_string(),
if_exists: false,
}),
Ddl,
),
(
Stmt::AlterFulltextIndex(AlterFulltextIndex {
span,
index_name: "idx".to_string(),
actions: vec![],
}),
Ddl,
),
(
Stmt::ShowFulltextIndex(ShowFulltextIndex {
span,
pattern: None,
from_schema: None,
}),
Ddl,
),
(
Stmt::DescribeFulltextIndex(DescribeFulltextIndex {
span,
index_name: "idx".to_string(),
}),
Ddl,
),
(
Stmt::CreateVectorIndex(CreateVectorIndex {
span,
index_name: "idx".to_string(),
schema_name: "s".to_string(),
field_name: "f".to_string(),
config: VectorIndexConfig {
vector_size: 3,
distance: VectorDistance::Cosine,
hnsw_m: None,
hnsw_ef_construct: None,
quantization: None,
quantile: None,
compression: None,
always_ram: None,
},
if_not_exists: false,
}),
Ddl,
),
(
Stmt::DropVectorIndex(DropVectorIndex {
span,
index_name: "idx".to_string(),
if_exists: false,
}),
Ddl,
),
(
Stmt::CreateUser(CreateUserStmt {
span,
username: "u".to_string(),
password: "p".to_string(),
if_not_exists: false,
}),
Dcl,
),
(
Stmt::AlterUser(AlterUserStmt {
span,
username: "u".to_string(),
password: None,
is_locked: None,
}),
Dcl,
),
(
Stmt::DropUser(DropUserStmt {
span,
username: "u".to_string(),
if_exists: false,
}),
Dcl,
),
(
Stmt::ChangePassword(ChangePasswordStmt {
span,
username: None,
old_password: "o".to_string(),
new_password: "n".to_string(),
}),
Dcl,
),
(
Stmt::Grant(GrantStmt {
span,
role: RoleType::User,
space_name: "s".to_string(),
username: "u".to_string(),
}),
Dcl,
),
(
Stmt::Revoke(RevokeStmt {
span,
role: RoleType::User,
space_name: "s".to_string(),
username: "u".to_string(),
}),
Dcl,
),
(
Stmt::DescribeUser(DescribeUserStmt {
span,
username: "u".to_string(),
}),
Dcl,
),
(Stmt::ShowUsers(ShowUsersStmt { span }), Dcl),
(
Stmt::ShowRoles(ShowRolesStmt {
span,
space_name: None,
}),
Dcl,
),
(
Stmt::Use(UseStmt {
span,
space: "s".to_string(),
}),
Admin,
),
(
Stmt::Show(ShowStmt {
span,
target: ShowTarget::Spaces,
}),
Admin,
),
(
Stmt::Explain(ExplainStmt {
span,
statement: Box::new(nested()),
format: ExplainFormat::Table,
analyze: false,
}),
Admin,
),
(
Stmt::Profile(ProfileStmt {
span,
statement: Box::new(nested()),
format: ExplainFormat::Table,
}),
Admin,
),
(Stmt::Analyze(AnalyzeStmt { span, space: None }), Admin),
(Stmt::ShowSessions(ShowSessionsStmt { span }), Admin),
(Stmt::ShowQueries(ShowQueriesStmt { span }), Admin),
(
Stmt::KillQuery(KillQueryStmt {
span,
session_id: 1,
plan_id: 1,
}),
Admin,
),
(
Stmt::ShowConfigs(ShowConfigsStmt { span, module: None }),
Admin,
),
(
Stmt::UpdateConfigs(UpdateConfigsStmt {
span,
module: None,
config_name: "c".to_string(),
config_value: ctx_expr(),
}),
Admin,
),
(
Stmt::ClearSpace(ClearSpaceStmt {
span,
space_name: "s".to_string(),
}),
Admin,
),
(
Stmt::Search(SearchStatement::new(
"idx".to_string(),
FulltextQueryExpr::Simple("q".to_string()),
)),
Search,
),
(
Stmt::SearchVector(SearchVectorStatement {
span,
index_name: "idx".to_string(),
query: vector_query.clone(),
threshold: None,
where_clause: None,
order_clause: None,
limit: None,
skip: None,
yield_clause: None,
}),
Search,
),
(
Stmt::LookupFulltext(LookupFulltext {
span,
schema_name: "s".to_string(),
index_name: "idx".to_string(),
query: "q".to_string(),
yield_clause: None,
limit: None,
}),
Search,
),
(
Stmt::MatchFulltext(MatchFulltext {
span,
pattern: "p".to_string(),
fulltext_condition: FulltextMatchCondition {
field: "f".to_string(),
query: "q".to_string(),
index_name: None,
},
yield_clause: None,
}),
Search,
),
(
Stmt::LookupVector(LookupVector {
span,
schema_name: "s".to_string(),
index_name: "idx".to_string(),
query: vector_query.clone(),
yield_clause: None,
limit: None,
}),
Search,
),
(
Stmt::MatchVector(MatchVector {
span,
pattern: "p".to_string(),
vector_condition: VectorMatchCondition {
field: "f".to_string(),
query: vector_query,
threshold: None,
},
yield_clause: None,
}),
Search,
),
(
Stmt::BeginTransaction(BeginTransactionStmt {
span,
read_only: None,
}),
Transaction,
),
(
Stmt::CommitTransaction(CommitTransactionStmt { span }),
Transaction,
),
(
Stmt::RollbackTransaction(RollbackTransactionStmt {
span,
savepoint_name: None,
}),
Transaction,
),
(
Stmt::Savepoint(SavepointStmt {
span,
name: "sp".to_string(),
}),
Transaction,
),
(
Stmt::ReleaseSavepoint(ReleaseSavepointStmt {
span,
name: "sp".to_string(),
}),
Transaction,
),
(
Stmt::Copy(CopyStmt {
span,
target: CopyTarget::Vertex("t".to_string()),
direction: CopyDirection::From,
file_paths: vec!["f.csv".to_string()],
by_column: false,
header: true,
delimiter: ',',
batch_size: None,
}),
Dml,
),
(
Stmt::Migrate(MigrateStmt::Plan(MigratePlanStmt {
span,
space: "s".to_string(),
label: "Person".to_string(),
is_edge: false,
from_version: 1,
to_version: 2,
})),
Ddl,
),
(Stmt::Checkpoint(CheckpointStmt { span }), Admin),
(
Stmt::PurgeExpired(PurgeExpiredStmt {
span,
space_name: "s".to_string(),
target: Some(PurgeTarget::Tag("t".to_string())),
}),
Ddl,
),
(
Stmt::LoadFrom(LoadFromStmt {
span,
headers: vec![],
source: ScanSource::File("data.csv".to_string()),
options: vec![],
return_clause: None,
}),
Dql,
),
(
Stmt::InQueryCall(InQueryCallStmt {
span,
func_name: "db_version".to_string(),
args: vec![],
yield_clause: None,
}),
Dql,
),
(
Stmt::ExportDatabase(ExportDatabaseStmt {
span,
path: "/tmp/db".to_string(),
options: vec![],
}),
Admin,
),
(
Stmt::ImportDatabase(ImportDatabaseStmt {
span,
path: "/tmp/db".to_string(),
}),
Admin,
),
(
Stmt::CreateMacro(CreateMacroStmt {
span,
name: "double".to_string(),
params: vec![MacroParam {
name: "x".to_string(),
default_value: None,
}],
body: ctx_expr(),
if_not_exists: false,
}),
Ddl,
),
(
Stmt::DropMacro(DropMacroStmt {
span,
name: "double".to_string(),
if_exists: false,
}),
Ddl,
),
(
Stmt::CreateType(CreateTypeStmt {
span,
name: "Age".to_string(),
underlying_type: DataType::Int,
underlying_type_text: "INT".to_string(),
if_not_exists: false,
}),
Ddl,
),
(
Stmt::DropType(DropTypeStmt {
span,
name: "Age".to_string(),
if_exists: false,
}),
Ddl,
),
(
Stmt::Extension(ExtensionStmt {
span,
action: ExtensionAction::Load,
name: "libudf.so".to_string(),
source: None,
}),
Admin,
),
];
assert_eq!(cases.len(), 80, "Stmt has 80 variants; update this test");
for (stmt, expected) in cases {
assert_eq!(
stmt.category(),
expected,
"category mismatch for {}",
stmt.kind()
);
}
}
#[test]
fn test_category_str_roundtrip() {
let categories = [
StmtCategory::Query,
StmtCategory::Dql,
StmtCategory::Dml,
StmtCategory::Ddl,
StmtCategory::Dcl,
StmtCategory::Admin,
StmtCategory::Search,
StmtCategory::Transaction,
];
let mut labels: Vec<&str> = categories.iter().map(|c| c.as_str()).collect();
labels.sort_unstable();
labels.dedup();
assert_eq!(labels.len(), categories.len(), "labels must be distinct");
for label in labels {
assert!(!label.is_empty());
}
}
#[test]
fn test_create_stmt() {
let stmt = Stmt::Create(CreateStmt {
span: Span::default(),
target: CreateTarget::Node {
variable: Some("n".to_string()),
labels: vec!["Person".to_string()],
properties: None,
},
if_not_exists: false,
});
assert!(matches!(stmt, Stmt::Create(_)));
}
#[test]
fn test_match_stmt() {
let stmt = Stmt::Match(MatchStmt {
span: Span::default(),
patterns: vec![],
where_clause: None,
return_clause: None,
order_by: None,
limit: None,
skip: None,
join_hint: None,
optional: false,
delete_clause: None,
});
assert!(matches!(stmt, Stmt::Match(_)));
}
#[test]
fn test_lookup_stmt() {
let stmt = Stmt::Lookup(LookupStmt {
span: Span::default(),
target: LookupTarget::Tag("Person".to_string()),
where_clause: None,
yield_clause: None,
});
assert!(matches!(stmt, Stmt::Lookup(_)));
}
#[test]
fn test_subgraph_stmt() {
let stmt = Stmt::Subgraph(SubgraphStmt {
span: Span::default(),
steps: Steps::Fixed(1),
from: FromClause {
span: Span::default(),
vertices: vec![],
},
over: None,
where_clause: None,
yield_clause: None,
with_prop: false,
});
assert!(matches!(stmt, Stmt::Subgraph(_)));
}
#[test]
fn test_find_path_stmt() {
use std::sync::Arc;
let expr_context = Arc::new(ExpressionAnalysisContext::new());
let expr = linkrs_core::types::expr::Expression::Variable("target".to_string());
let expr_meta = linkrs_core::types::expr::ExpressionMeta::new(expr);
let expr_id = expr_context.register_expression(expr_meta);
let to_expr =
linkrs_core::types::expr::contextual::ContextualExpression::new(expr_id, expr_context);
let stmt = Stmt::FindPath(FindPathStmt {
span: Span::default(),
from: FromClause {
span: Span::default(),
vertices: vec![],
},
to: to_expr,
over: None,
where_clause: None,
shortest: true,
max_steps: None,
limit: None,
skip: None,
yield_clause: None,
weight_expression: None,
heuristic_expression: None,
with_loop: false,
with_trail: false,
with_walk: false,
});
assert!(matches!(stmt, Stmt::FindPath(_)));
}