1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! DDL Flagger walk.
use selene_core::feature_register::FeatureId;
use crate::{
DdlStatement,
ast::ddl::{DropBehavior, TypePropertyConstraint, TypePropertyDef},
};
use super::{FeatureUse, expr, record_feature};
pub(crate) fn statement(statement: &DdlStatement, uses: &mut Vec<FeatureUse>) {
match statement {
DdlStatement::CreateGraph {
or_replace,
if_not_exists,
span,
..
} => {
let _ = or_replace;
record_feature(uses, FeatureId::GG01, *span);
record_feature(uses, FeatureId::GC04, *span);
if *if_not_exists {
record_feature(uses, FeatureId::GC05, *span);
}
}
DdlStatement::DropGraph {
if_exists, span, ..
} => {
// BRIEF-152 / audit Item 10: DROP GRAPH ships as the IM_DROP_GRAPH
// factory-reset extension (a supported selene-db vendor flag), NOT
// GC04. CREATE GRAPH stays on GC04 (unsupported) so it remains
// parse-rejected under D1 single-graph. IF EXISTS is informational
// under D1 (the session graph always exists), so it carries no extra
// flag — both DROP GRAPH and DROP GRAPH IF EXISTS flag IM_DROP_GRAPH.
let _ = if_exists;
record_feature(uses, FeatureId::IM_DROP_GRAPH, *span);
}
DdlStatement::CreateNodeType {
key_label_set,
extends,
or_replace,
if_not_exists,
properties,
span,
..
} => {
let _ = or_replace;
type_ddl(*span, key_label_set.is_some(), uses);
if *if_not_exists {
record_feature(uses, FeatureId::GC03, *span);
}
if extends.is_some() {
record_feature(uses, FeatureId::IM_EXTENDS, *span);
}
property_defs(properties, uses);
}
DdlStatement::CreateEdgeType {
key_label_set,
extends,
or_replace,
if_not_exists,
properties,
span,
..
} => {
let _ = or_replace;
type_ddl(*span, key_label_set.is_some(), uses);
if *if_not_exists {
record_feature(uses, FeatureId::GC03, *span);
}
if extends.is_some() {
record_feature(uses, FeatureId::IM_EXTENDS, *span);
}
property_defs(properties, uses);
}
DdlStatement::DropNodeType {
if_exists,
behavior,
span,
..
}
| DdlStatement::DropEdgeType {
if_exists,
behavior,
span,
..
} => {
type_ddl(*span, false, uses);
if *if_exists {
record_feature(uses, FeatureId::GC03, *span);
}
// GQL Flagger (clause 24.6): CASCADE is a selene-db impl-defined
// addition, not ISO GQL, so it must flag on every use. RESTRICT and
// the default carry only the existing type-DDL flags.
if matches!(behavior, DropBehavior::Cascade) {
record_feature(uses, FeatureId::IM_DROP_CASCADE, *span);
}
}
DdlStatement::CreateIndex { span, .. } | DdlStatement::DropIndex { span, .. } => {
record_feature(uses, FeatureId::IM_INDEX_DDL, *span);
}
// GQL Flagger (clause 24.6): TRUNCATE is a selene-db impl-defined
// addition, not ISO GQL, so it must flag on every use.
DdlStatement::TruncateNodeType { span, .. }
| DdlStatement::TruncateEdgeType { span, .. } => {
record_feature(uses, FeatureId::IM_TRUNCATE, *span);
}
DdlStatement::ShowNodeTypes(span) | DdlStatement::ShowEdgeTypes(span) => {
type_ddl(*span, false, uses);
}
DdlStatement::ShowIndexes(_) | DdlStatement::ShowProcedures(_) => {}
}
}
fn type_ddl(span: crate::SourceSpan, explicit_key_label_set: bool, uses: &mut Vec<FeatureUse>) {
// ISO/IEC 39075:2024 §18.2/18.3: a closed-graph type-DDL statement uses an
// explicit `<node/edge type name>` (the `:Name` after `NODE/EDGE TYPE`),
// which is GG20 "Explicit element type names" under a closed graph type
// (GG02).
record_feature(uses, FeatureId::GG02, span);
record_feature(uses, FeatureId::GG20, span);
// GG21 "Explicit element type key label sets" flags only when the source
// contains the explicit `<node/edge type key label set>` production (`[
// <label set phrase> ] <implies>`, the `=>` marker). The bare `:Name` form
// keeps the key label set *implied* per §18.2 SR5c — GG20-only, no GG21.
if explicit_key_label_set {
record_feature(uses, FeatureId::GG21, span);
}
}
fn property_defs(properties: &[TypePropertyDef], uses: &mut Vec<FeatureUse>) {
for property in properties {
expr::gql_type(&property.gql_type, property.span, uses);
for constraint in &property.constraints {
property_constraint(constraint, uses);
}
}
}
fn property_constraint(constraint: &TypePropertyConstraint, uses: &mut Vec<FeatureUse>) {
match constraint {
TypePropertyConstraint::Default(value, _) => expr::value(value, uses),
TypePropertyConstraint::NotNull(_)
| TypePropertyConstraint::Immutable(_)
| TypePropertyConstraint::Unique(_)
| TypePropertyConstraint::Indexed { .. } => {}
}
}