pgevolve_core/parse/builder/
create_seq_stmt.rs1use pg_query::NodeEnum;
4use pg_query::protobuf::{AConst, CreateSeqStmt, DefElem, a_const};
5
6use crate::identifier::Identifier;
7use crate::ir::column_type::ColumnType;
8use crate::ir::sequence::{Sequence, SequenceOwner};
9use crate::parse::builder::shared;
10use crate::parse::error::{ParseError, SourceLocation};
11
12pub fn build_sequence(
14 stmt: &CreateSeqStmt,
15 default_schema: Option<&Identifier>,
16 location: &SourceLocation,
17) -> Result<Sequence, ParseError> {
18 let range = stmt
19 .sequence
20 .as_ref()
21 .ok_or_else(|| ParseError::Structural {
22 location: location.clone(),
23 message: "CREATE SEQUENCE missing sequence name".into(),
24 })?;
25 let qname = shared::resolve_qname(range, default_schema, location)?;
26
27 let mut data_type = ColumnType::BigInt;
29 let mut start: i64 = 1;
30 let mut increment: i64 = 1;
31 let mut min_value: Option<i64> = None;
32 let mut max_value: Option<i64> = None;
33 let mut cache: i64 = 1;
34 let mut cycle = false;
35 let mut owned_by: Option<SequenceOwner> = None;
36
37 for opt in &stmt.options {
38 let Some(NodeEnum::DefElem(de)) = opt.node.as_ref() else {
39 continue;
40 };
41 match de.defname.as_str() {
42 "as" => {
43 if let Some(t) = type_name_of(de)
44 && let Ok(parsed) = ColumnType::parse_from_pg_type_string(&t)
45 {
46 data_type = parsed;
47 }
48 }
49 "start" => {
50 if let Some(v) = i64_of(de) {
51 start = v;
52 }
53 }
54 "increment" => {
55 if let Some(v) = i64_of(de) {
56 increment = v;
57 }
58 }
59 "minvalue" => {
60 min_value = i64_of(de);
61 }
62 "maxvalue" => {
63 max_value = i64_of(de);
64 }
65 "cache" => {
66 if let Some(v) = i64_of(de) {
67 cache = v;
68 }
69 }
70 "cycle" => {
71 cycle = bool_of(de).unwrap_or(true);
72 }
73 "owned_by" => {
74 owned_by = parse_owned_by(de, default_schema, location)?;
75 }
76 _ => {}
77 }
78 }
79
80 Ok(Sequence {
85 qname,
86 data_type,
87 start,
88 increment,
89 min_value,
90 max_value,
91 cache,
92 cycle,
93 owned_by,
94 comment: None,
95 owner: None,
96 grants: vec![],
97 })
98}
99
100fn i64_of(de: &DefElem) -> Option<i64> {
101 let arg = de.arg.as_ref()?.node.as_ref()?;
102 match arg {
103 NodeEnum::Integer(i) => Some(i64::from(i.ival)),
104 NodeEnum::Float(f) => f.fval.parse::<i64>().ok(),
105 NodeEnum::AConst(c) => aconst_int(c),
106 _ => None,
107 }
108}
109
110fn bool_of(de: &DefElem) -> Option<bool> {
111 let arg = de.arg.as_ref()?.node.as_ref()?;
112 match arg {
113 NodeEnum::Boolean(b) => Some(b.boolval),
114 NodeEnum::Integer(i) => Some(i.ival != 0),
115 NodeEnum::AConst(c) => match c.val.as_ref()? {
116 a_const::Val::Boolval(b) => Some(b.boolval),
117 a_const::Val::Ival(i) => Some(i.ival != 0),
118 _ => None,
119 },
120 _ => None,
121 }
122}
123
124fn aconst_int(c: &AConst) -> Option<i64> {
125 match c.val.as_ref()? {
126 a_const::Val::Ival(i) => Some(i64::from(i.ival)),
127 a_const::Val::Fval(f) => f.fval.parse::<i64>().ok(),
128 _ => None,
129 }
130}
131
132fn type_name_of(de: &DefElem) -> Option<String> {
133 let arg = de.arg.as_ref()?.node.as_ref()?;
134 if let NodeEnum::TypeName(tn) = arg {
135 return shared::render_type_name_to_string(tn);
136 }
137 None
138}
139
140fn parse_owned_by(
142 de: &DefElem,
143 default_schema: Option<&Identifier>,
144 location: &SourceLocation,
145) -> Result<Option<SequenceOwner>, ParseError> {
146 let Some(arg) = de.arg.as_ref().and_then(|a| a.node.as_ref()) else {
147 return Ok(None);
148 };
149 let NodeEnum::List(list) = arg else {
150 return Ok(None);
151 };
152 let parts: Vec<&str> = list
153 .items
154 .iter()
155 .filter_map(|n| match n.node.as_ref() {
156 Some(NodeEnum::String(s)) => Some(s.sval.as_str()),
157 _ => None,
158 })
159 .collect();
160 match parts.as_slice() {
161 [first] if first.eq_ignore_ascii_case("none") => Ok(None),
163 [table, column] => {
164 let schema = default_schema
165 .cloned()
166 .ok_or_else(|| ParseError::UnqualifiedName {
167 location: location.clone(),
168 })?;
169 Ok(Some(SequenceOwner {
170 table: crate::identifier::QualifiedName::new(
171 schema,
172 shared::ident(table, location)?,
173 ),
174 column: shared::ident(column, location)?,
175 }))
176 }
177 [schema, table, column] => Ok(Some(SequenceOwner {
178 table: crate::identifier::QualifiedName::new(
179 shared::ident(schema, location)?,
180 shared::ident(table, location)?,
181 ),
182 column: shared::ident(column, location)?,
183 })),
184 _ => Err(ParseError::Structural {
185 location: location.clone(),
186 message: "OWNED BY must reference table.column or schema.table.column".into(),
187 }),
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use std::path::PathBuf;
195
196 fn loc() -> SourceLocation {
197 SourceLocation::new(PathBuf::from("test.sql"), 1, 1)
198 }
199
200 fn build(sql: &str) -> Sequence {
201 let parsed = pg_query::parse(sql).expect("parses");
202 let stmt = parsed
203 .protobuf
204 .stmts
205 .into_iter()
206 .next()
207 .and_then(|raw| raw.stmt)
208 .and_then(|n| n.node)
209 .expect("stmt");
210 let NodeEnum::CreateSeqStmt(s) = stmt else {
211 panic!()
212 };
213 build_sequence(&s, None, &loc()).expect("builds")
214 }
215
216 #[test]
217 fn defaults_for_bare_create() {
218 let s = build("CREATE SEQUENCE app.s1;");
219 assert_eq!(s.qname.to_string(), "app.s1");
220 assert_eq!(s.data_type, ColumnType::BigInt);
221 assert_eq!(s.start, 1);
222 assert_eq!(s.increment, 1);
223 assert_eq!(s.cache, 1);
224 assert!(!s.cycle);
225 assert!(s.owned_by.is_none());
226 }
227
228 #[test]
229 fn explicit_options_extracted() {
230 let s = build(
231 "CREATE SEQUENCE app.s1 AS integer INCREMENT BY 2 START WITH 10 MINVALUE 5 \
232 MAXVALUE 1000 CACHE 50 CYCLE;",
233 );
234 assert_eq!(s.data_type, ColumnType::Integer);
235 assert_eq!(s.start, 10);
236 assert_eq!(s.increment, 2);
237 assert_eq!(s.min_value, Some(5));
238 assert_eq!(s.max_value, Some(1000));
239 assert_eq!(s.cache, 50);
240 assert!(s.cycle);
241 }
242
243 #[test]
244 fn owned_by_extracted() {
245 let s = build("CREATE SEQUENCE app.users_id_seq OWNED BY app.users.id;");
246 let owner = s.owned_by.expect("owned_by present");
247 assert_eq!(owner.table.to_string(), "app.users");
248 assert_eq!(owner.column.as_str(), "id");
249 }
250
251 #[test]
252 fn owned_by_none_yields_no_owner() {
253 let s = build("CREATE SEQUENCE app.s OWNED BY NONE;");
254 assert!(s.owned_by.is_none());
255 }
256}