Skip to main content

sql_fun_sqlast/sem/
alter_table.rs

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/// analyzed alter table statement
14#[derive(Debug, Clone, derive_getters::Getters)]
15pub struct AlterTable {
16    table: TableName,
17    commands: Vec<AlterObjSubCommand>,
18}
19
20/// analyzed alter view statement
21#[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    // FIXME: check table, column existing
119
120    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        // alter table ...
139        //   alter column value set default(0)
140        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/// analyze [`crate::syn::AlterTableStmt`]
365#[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}