1use sql_fun_core::IVec;
2
3use crate::{
4 sem::{
5 AlterSequence, AnalizeConstraint, AstAndContextPair, ColumnName, Constraint, FromClause,
6 FullName, SemScalarExpr, ViewName, WithClause, analyze_scaler_expr,
7 },
8 syn::{AlterTableType, ListOpt, ObjectType, Opt, ScanToken},
9};
10
11use super::{AnalysisError, AnalysisProblem, ParseContext, SemAst, create_table::TableName};
12
13#[derive(Debug, Clone, derive_getters::Getters)]
15pub struct AlterTable {
16 table: TableName,
17 commands: Vec<AlterObjSubCommand>,
18}
19
20#[derive(Debug, Clone, derive_getters::Getters)]
22pub struct AlterView {
23 view: ViewName,
24 commands: Vec<AlterObjSubCommand>,
25}
26
27#[derive(Debug, Clone)]
28pub enum AlterObjSubCommand {
29 ChangeOwner(String),
30 SetColumnDefault(ColumnName, Box<SemScalarExpr>),
31 AddConstraint(Constraint),
32 ClusterOn(String),
33}
34
35fn analyze_alter_obj_change_owner<TParseContext>(
36 _context: &TParseContext,
37 cmd: crate::syn::AlterTableCmd,
38) -> Result<AlterObjSubCommand, AnalysisError> {
39 let Some(new_owner) = cmd.get_newowner().get_rolename() else {
40 AnalysisError::raise_unexpected_none("alter_table_cmd.newwoner")?
41 };
42 Ok(AlterObjSubCommand::ChangeOwner(new_owner))
43}
44
45#[cfg(test)]
46mod test_analyze_alter_obj_change_owner {
47 use crate::{
48 sem::alter_table::analyze_alter_obj_change_owner,
49 test_helpers::{SynBuilder, TestParseContext, test_context},
50 };
51 use testresult::TestResult;
52
53 #[rstest::rstest]
54 fn test_analyze_alter_obj_change_owner(test_context: TestParseContext) -> TestResult {
55 let alter_table_cmd = SynBuilder::new().alter_table_cmd_change_owner("test");
56 let _result = analyze_alter_obj_change_owner(&test_context, alter_table_cmd)?;
57 Ok(())
58 }
59}
60
61fn analyze_alter_obj_add_constraint<TParseContext>(
62 context: &mut TParseContext,
63 target_table: &TableName,
64 cmd: crate::syn::AlterTableCmd,
65) -> Result<AlterObjSubCommand, AnalysisError>
66where
67 TParseContext: ParseContext,
68{
69 let _constraint_name = cmd.get_name();
70 let Some(constraint) = cmd.get_def().as_constraint().as_inner() else {
71 AnalysisError::raise_unexpected_none("alter_table_cmd.def as constraint")?
72 };
73 let constraint = Constraint::analyze(context, Some(target_table), constraint)?;
74 Ok(AlterObjSubCommand::AddConstraint(constraint))
75}
76
77#[cfg(test)]
78mod test_analyze_alter_obj_add_constraint {
79 use testresult::TestResult;
80
81 use crate::{
82 sem::{CreateTable, FullName, TableName},
83 test_helpers::{SynBuilder, TestParseContext, test_context},
84 };
85
86 #[rstest::rstest]
87 fn test_analyze_alter_obj_add_constraint(mut test_context: TestParseContext) -> TestResult {
88 let builder = SynBuilder::new();
89 let constraint = builder.constraint_primary_key();
90 let alter_table_cmd = builder.alter_table_cmd_add_constraint(constraint);
91 let table_name = TableName::from(FullName::with_schema("public", "table1"));
92 let create_table = CreateTable::new(table_name.clone(), vec![]);
93 test_context.set_get_table_impl_result(table_name.full_name(), &Some(create_table));
94 let _result = super::analyze_alter_obj_add_constraint(
95 &mut test_context,
96 &table_name,
97 alter_table_cmd,
98 )?;
99 Ok(())
100 }
101}
102
103fn analyze_alter_obj_column_default<TParseContext>(
104 mut context: TParseContext,
105 cmd: crate::syn::AlterTableCmd,
106 tokens: &IVec<ScanToken>,
107) -> Result<(AlterObjSubCommand, TParseContext), AnalysisError>
108where
109 TParseContext: ParseContext,
110{
111 let column_name = ColumnName::from(cmd.get_name().clone());
112 let with_clause = WithClause::default();
113 let from_clause = FromClause::default();
114 let (expr, new_context) =
115 analyze_scaler_expr(context, &with_clause, &from_clause, cmd.get_def(), tokens)?;
116 context = new_context;
117
118 Ok((
121 AlterObjSubCommand::SetColumnDefault(column_name, Box::new(expr)),
122 context,
123 ))
124}
125
126#[cfg(test)]
127mod test_analyze_alter_obj_column_default {
128 use crate::{
129 sem::{ScalarConstExpr, SemScalarExpr, alter_table::AlterObjSubCommand},
130 syn::NodeInner,
131 test_helpers::{SynBuilder, TestParseContext, test_context},
132 };
133 use sql_fun_core::IVec;
134 use testresult::TestResult;
135
136 #[rstest::rstest]
137 fn test_analyze_alter_obj_column_default(test_context: TestParseContext) -> TestResult {
138 let builder = SynBuilder::new();
141 let value = builder.const_int4(0);
142 let cmd =
143 builder.alter_table_cmd_set_column_default("value", NodeInner::AConst(value).into());
144 let tokens = IVec::default();
145
146 let (res, _) = super::analyze_alter_obj_column_default(test_context, cmd, &tokens)?;
147 let AlterObjSubCommand::SetColumnDefault(col_name, expr) = res else {
148 panic!("unexpected respose {res:#?}");
149 };
150 assert_eq!(col_name.as_str(), "value");
151 let SemScalarExpr::Const(ScalarConstExpr::Integer(val)) = *expr else {
152 panic!("unexpected expr {expr:#?}");
153 };
154 assert_eq!(val, 0);
155
156 Ok(())
157 }
158}
159
160pub fn analyze_alter_table_cmd<TParseContext>(
161 mut context: TParseContext,
162 table_name: &TableName,
163 cmd: crate::syn::AlterTableCmd,
164 subtyp: AlterTableType,
165 tokens: &IVec<ScanToken>,
166) -> Result<(AlterObjSubCommand, TParseContext), AnalysisError>
167where
168 TParseContext: ParseContext,
169{
170 let sub_cmd = match subtyp {
171 AlterTableType::AtChangeOwner => analyze_alter_obj_change_owner(&context, cmd)?,
172 AlterTableType::AtColumnDefault => {
173 let (column_default, new_context) =
174 analyze_alter_obj_column_default(context, cmd, tokens)?;
175 context = new_context;
176 column_default
177 }
178 AlterTableType::AtAddConstraint => {
179 analyze_alter_obj_add_constraint(&mut context, table_name, cmd)?
180 }
181 AlterTableType::AtClusterOn => {
182 let index_name = cmd.get_name();
183 if context.get_index(&index_name).is_none() {
184 context.report_problem(AnalysisProblem::index_not_found(&index_name))?;
185 }
186 AlterObjSubCommand::ClusterOn(cmd.get_name())
187 }
188
189 _ => todo!("{subtyp:?}"),
190 };
191 Ok((sub_cmd, context))
192}
193
194pub fn analyze_alter_table_statement<TParseContext>(
195 mut context: TParseContext,
196 _parent_schema: &Option<String>,
197 syn: crate::syn::AlterTableStmt,
198 tokens: &IVec<ScanToken>,
199) -> Result<AstAndContextPair<TParseContext>, AnalysisError>
200where
201 TParseContext: ParseContext,
202{
203 let Some(rel) = syn.get_relation().as_inner() else {
204 AnalysisError::raise_unexpected_none("alter_table_stmt.relation")?
205 };
206 let table_name = TableName::try_from(rel)?;
207 if context.get_table(&table_name).is_none() {
208 context.report_problem(AnalysisProblem::relation_not_found(&table_name))?;
209 }
210
211 let Some(cmds) = syn.get_cmds().map(|c| c.as_alter_table_cmd()) else {
212 AnalysisError::raise_unexpected_none("alter_table_stmt.cmds")?
213 };
214 let mut sub_cmds = Vec::new();
215 for cmd in cmds {
216 let Some(subtyp) = cmd.get_subtype().as_inner() else {
217 AnalysisError::raise_unexpected_none("alter_table_cmd.subtype")?
218 };
219 let (sub_cmd, new_context) =
220 analyze_alter_table_cmd(context, &table_name, cmd, subtyp, tokens)?;
221 context = new_context;
222 sub_cmds.push(sub_cmd);
223 }
224 let alter_table = AlterTable {
225 table: table_name,
226 commands: sub_cmds,
227 };
228 context = context.apply_alter_table(&alter_table)?;
229 Ok(AstAndContextPair::new(
230 SemAst::AlterTable(alter_table),
231 context,
232 ))
233}
234
235#[cfg(test)]
236mod test_analyze_alter_table_statement {
237 use crate::{
238 AstAndContextPair,
239 sem::{FullName, SemAst},
240 syn::{AliasOpt, NodeInner, ObjectType},
241 test_helpers::{SynBuilder, TestParseContext, test_context},
242 };
243 use sql_fun_core::IVec;
244 use testresult::TestResult;
245
246 #[rstest::rstest]
247 fn test_analyze_alter_table_statement(mut test_context: TestParseContext) -> TestResult {
248 test_context.setup_table("table1", &[]);
249
250 let builder = SynBuilder::new();
251 let relation = builder.range_var(
252 &FullName::with_schema("public", "table1"),
253 &AliasOpt::none(),
254 );
255 let cmd = builder.alter_table_cmd_set_column_default(
256 "col1",
257 NodeInner::AConst(builder.const_int4(10)).into(),
258 );
259 let cmds = builder.as_node_list(&[NodeInner::AlterTableCmd(cmd).into()]);
260 let alter_table_stmt =
261 builder.alter_table_stmt(relation.into(), cmds, ObjectType::ObjectTable.into());
262 let tokens = IVec::default();
263 let AstAndContextPair(res, ctx) =
264 super::analyze_alter_table_statement(test_context, &None, alter_table_stmt, &tokens)?;
265 let SemAst::AlterTable(at) = res else {
266 panic!("unexpected type {res:?}")
267 };
268 assert_eq!(at.commands.len(), 1);
269 assert_eq!(ctx.alter_tables_len(), 1);
270 Ok(())
271 }
272}
273
274#[tracing::instrument(skip(context, _parent_schema, syn))]
275pub fn analyze_alter_view_statement<TParseContext>(
276 mut context: TParseContext,
277 _parent_schema: &Option<String>,
278 as_matview: bool,
279 syn: crate::syn::AlterTableStmt,
280) -> Result<AstAndContextPair<TParseContext>, AnalysisError>
281where
282 TParseContext: ParseContext,
283{
284 let Some(rel) = syn.get_relation().as_inner() else {
285 AnalysisError::raise_unexpected_none("alter_table_stmt.relation")?
286 };
287 let view_name = ViewName::from(rel);
288 if let Some(view) = context.get_view(&view_name) {
289 if as_matview && !view.is_materialized() {
290 context.report_problem(AnalysisProblem::view_not_matview(&view_name))?;
291 }
292 } else {
293 context.report_problem(AnalysisProblem::view_not_found(&view_name))?;
294 }
295
296 let Some(cmds) = syn.get_cmds().map(|c| c.as_alter_table_cmd()) else {
297 AnalysisError::raise_unexpected_none("alter_table_stmt.cmds")?
298 };
299 let mut sub_cmds = Vec::new();
300 for cmd in cmds {
301 let Some(subtyp) = cmd.get_subtype().as_inner() else {
302 AnalysisError::raise_unexpected_none("alter_table_cmd.subtype")?
303 };
304 let sub_cmd = match subtyp {
305 AlterTableType::AtChangeOwner => analyze_alter_obj_change_owner(&context, cmd)?,
306
307 _ => todo!("{subtyp:?}"),
308 };
309 sub_cmds.push(sub_cmd);
310 }
311 let alter_view = AlterView {
312 view: view_name,
313 commands: sub_cmds,
314 };
315
316 context = context.apply_alter_view(&alter_view)?;
317 Ok(AstAndContextPair::new(
318 SemAst::AlterView(alter_view),
319 context,
320 ))
321}
322
323pub fn analyze_alter_seq_statement<TParseContext>(
324 mut context: TParseContext,
325 _parent_schema: &Option<String>,
326 syn: crate::syn::AlterTableStmt,
327) -> Result<AstAndContextPair<TParseContext>, AnalysisError>
328where
329 TParseContext: ParseContext,
330{
331 let Some(rel) = syn.get_relation().as_inner() else {
332 AnalysisError::raise_unexpected_none("alter_table_stmt.relation")?
333 };
334 let seq_name = FullName::try_from(rel)?;
335 if context.get_sequence(&seq_name).is_none() {
336 context.report_problem(AnalysisProblem::sequence_not_found(&seq_name))?;
337 }
338 let Some(cmds) = syn.get_cmds().map(|c| c.as_alter_table_cmd()) else {
339 AnalysisError::raise_unexpected_none("alter_table_stmt.cmds")?
340 };
341 let mut sub_cmds = Vec::new();
342
343 for cmd in cmds {
344 let Some(subtyp) = cmd.get_subtype().as_inner() else {
345 AnalysisError::raise_unexpected_none("alter_table_cmd.subtype")?
346 };
347 let sub_cmd = match subtyp {
348 AlterTableType::AtChangeOwner => analyze_alter_obj_change_owner(&context, cmd)?,
349
350 _ => todo!("{subtyp:?}"),
351 };
352 sub_cmds.push(sub_cmd);
353 }
354
355 let alter_seq = AlterSequence::new(&seq_name, &sub_cmds);
356
357 context = context.apply_alter_sequence(&alter_seq)?;
358 Ok(AstAndContextPair::new(
359 SemAst::AlterSequence(alter_seq),
360 context,
361 ))
362}
363
364#[tracing::instrument(skip(context, parent_schema, syn))]
366pub fn analyze_alter_statement<TParseContext>(
367 context: TParseContext,
368 parent_schema: &Option<String>,
369 syn: crate::syn::AlterTableStmt,
370 tokens: &IVec<ScanToken>,
371) -> Result<AstAndContextPair<TParseContext>, AnalysisError>
372where
373 TParseContext: ParseContext,
374{
375 let Some(obj_type) = syn.get_objtype().as_inner() else {
376 AnalysisError::raise_unexpected_none("alter_table_stmt.objtype")?
377 };
378 match obj_type {
379 ObjectType::ObjectTable => {
380 analyze_alter_table_statement(context, parent_schema, syn, tokens)
381 }
382 ObjectType::ObjectView => analyze_alter_view_statement(context, parent_schema, false, syn),
383 ObjectType::ObjectSequence => analyze_alter_seq_statement(context, parent_schema, syn),
384 ObjectType::ObjectMatview => {
385 analyze_alter_view_statement(context, parent_schema, true, syn)
386 }
387 _ => todo!("{obj_type:?}"),
388 }
389}