sql_type/
schema.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13//! Facility for parsing SQL schemas into a terse format that can be used
14//! for typing statements.
15//!
16//! ```
17//! use sql_type::{schema::parse_schemas, TypeOptions, SQLDialect, Issues};
18//! let schemas = "
19//!     -- Table structure for table `events`
20//!     DROP TABLE IF EXISTS `events`;
21//!     CREATE TABLE `events` (
22//!       `id` bigint(20) NOT NULL,
23//!       `user` int(11) NOT NULL,
24//!       `event_key` int(11) NOT NULL,
25//!       `time` datetime NOT NULL
26//!     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
27//!
28//!     -- Table structure for table `events_keys`
29//!     DROP TABLE IF EXISTS `event_keys`;
30//!     CREATE TABLE `event_keys` (
31//!       `id` int(11) NOT NULL,
32//!       `name` text NOT NULL
33//!     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
34//!
35//!     -- Stand-in structure for view `events_view`
36//!     -- (See below for the actual view)
37//!     DROP VIEW IF EXISTS `events_view`;
38//!     CREATE TABLE `events_view` (
39//!         `id` int(11),
40//!         `user` int(11) NOT NULL,
41//!         `event_key` text NOT NULL,
42//!         `time` datetime NOT NULL
43//!     );
44//!
45//!     -- Indexes for table `events`
46//!     ALTER TABLE `events`
47//!       ADD PRIMARY KEY (`id`),
48//!       ADD KEY `time` (`time`),
49//!       ADD KEY `event_key` (`event_key`);
50//!
51//!     -- Indexes for table `event_keys`
52//!     ALTER TABLE `event_keys`
53//!       ADD PRIMARY KEY (`id`);
54//!
55//!     -- Constraints for table `events`
56//!     ALTER TABLE `events`
57//!       ADD CONSTRAINT `event_key` FOREIGN KEY (`event_key`) REFERENCES `event_keys` (`id`);
58//!
59//!     -- Structure for view `events_view`
60//!     DROP TABLE IF EXISTS `events_view`;
61//!     DROP VIEW IF EXISTS `events_view`;
62//!     CREATE ALGORITHM=UNDEFINED DEFINER=`phpmyadmin`@`localhost`
63//!         SQL SECURITY DEFINER VIEW `events_view` AS
64//!         SELECT
65//!             `events`.`id` AS `id`,
66//!             `events`.`user` AS `user`,
67//!             `event_keys`.`name` AS `event_key`,
68//!             `events`.`time` AS `time`
69//!         FROM `events`, `event_keys`
70//!         WHERE `events`.`event_key` = `event_keys`.`id`;
71//!     ";
72//!
73//! let mut issues = Issues::new(schemas);
74//! let schemas = parse_schemas(schemas,
75//!     &mut issues,
76//!     &TypeOptions::new().dialect(SQLDialect::MariaDB));
77//!
78//! assert!(issues.is_ok());
79//!
80//! for (name, schema) in schemas.schemas {
81//!     println!("{name}: {schema:?}")
82//! }
83//! ```
84
85use crate::{
86    type_::{BaseType, FullType},
87    type_statement,
88    typer::unqualified_name,
89    Type, TypeOptions,
90};
91use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
92use sql_parse::{parse_statements, DataType, Expression, Identifier, Issues, Span, Spanned};
93
94/// A column in a schema
95#[derive(Debug)]
96pub struct Column<'a> {
97    pub identifier: Identifier<'a>,
98    /// Type of the column
99    pub type_: FullType<'a>,
100    /// True if the column is auto_increment
101    pub auto_increment: bool,
102    pub default: bool,
103    pub as_: Option<alloc::boxed::Box<Expression<'a>>>,
104}
105
106/// Schema representing a table or view
107#[derive(Debug)]
108pub struct Schema<'a> {
109    /// Span of identifier
110    pub identifier_span: Span,
111    /// List of columns
112    pub columns: Vec<Column<'a>>,
113    /// True if this is a view instead of a table
114    pub view: bool,
115}
116
117impl<'a> Schema<'a> {
118    pub fn get_column(&self, identifier: &str) -> Option<&Column<'a>> {
119        self.columns
120            .iter()
121            .find(|&column| column.identifier.value == identifier)
122    }
123    pub fn get_column_mut(&mut self, identifier: &str) -> Option<&mut Column<'a>> {
124        self.columns
125            .iter_mut()
126            .find(|column| column.identifier.value == identifier)
127    }
128}
129
130/// A procedure
131#[derive(Debug)]
132pub struct Procedure {}
133
134/// A function
135#[derive(Debug)]
136pub struct Functions {}
137
138#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
139pub struct IndexKey<'a> {
140    pub table: Option<Identifier<'a>>,
141    pub index: Identifier<'a>,
142}
143
144/// A description of tables, view, procedures and function in a schemas definition file
145#[derive(Debug, Default)]
146pub struct Schemas<'a> {
147    /// Map from name to Tables or views
148    pub schemas: BTreeMap<Identifier<'a>, Schema<'a>>,
149    /// Map from name to procedure
150    pub procedures: BTreeMap<Identifier<'a>, Procedure>,
151    /// Map from name to function
152    pub functions: BTreeMap<Identifier<'a>, Functions>,
153    /// Map from (table, index) to location
154    pub indices: BTreeMap<IndexKey<'a>, Span>,
155}
156
157pub(crate) fn parse_column<'a>(
158    data_type: DataType<'a>,
159    identifier: Identifier<'a>,
160    _issues: &mut Issues<'a>,
161) -> Column<'a> {
162    let mut not_null = false;
163    let mut unsigned = false;
164    let mut auto_increment = false;
165    let mut default = false;
166    let mut _as = None;
167    for p in data_type.properties {
168        match p {
169            sql_parse::DataTypeProperty::Signed(_) => unsigned = false,
170            sql_parse::DataTypeProperty::Unsigned(_) => unsigned = true,
171            sql_parse::DataTypeProperty::Null(_) => not_null = false,
172            sql_parse::DataTypeProperty::NotNull(_) => not_null = true,
173            sql_parse::DataTypeProperty::AutoIncrement(_) => auto_increment = true,
174            sql_parse::DataTypeProperty::As((_, e)) => _as = Some(e),
175            sql_parse::DataTypeProperty::Default(_) => default = true,
176            _ => {}
177        }
178    }
179    let type_ = match data_type.type_ {
180        sql_parse::Type::TinyInt(v) => {
181            if !unsigned && matches!(v, Some((1, _))) {
182                BaseType::Bool.into()
183            } else if unsigned {
184                Type::U8
185            } else {
186                Type::I8
187            }
188        }
189        sql_parse::Type::SmallInt(_) => {
190            if unsigned {
191                Type::U16
192            } else {
193                Type::I16
194            }
195        }
196        sql_parse::Type::Int(_) => {
197            if unsigned {
198                Type::U32
199            } else {
200                Type::I32
201            }
202        }
203        sql_parse::Type::BigInt(_) => {
204            if unsigned {
205                Type::U64
206            } else {
207                Type::I64
208            }
209        }
210        sql_parse::Type::Char(_) => BaseType::String.into(),
211        sql_parse::Type::VarChar(_) => BaseType::String.into(),
212        sql_parse::Type::TinyText(_) => BaseType::String.into(),
213        sql_parse::Type::MediumText(_) => BaseType::String.into(),
214        sql_parse::Type::Text(_) => BaseType::String.into(),
215        sql_parse::Type::LongText(_) => BaseType::String.into(),
216        sql_parse::Type::Enum(e) => Type::Enum(Arc::new(e.into_iter().map(|s| s.value).collect())),
217        sql_parse::Type::Set(s) => Type::Set(Arc::new(s.into_iter().map(|s| s.value).collect())),
218        sql_parse::Type::Float(_) => Type::F32,
219        sql_parse::Type::Double(_) => Type::F64,
220        sql_parse::Type::DateTime(_) => BaseType::DateTime.into(),
221        sql_parse::Type::Timestamp(_) => BaseType::TimeStamp.into(),
222        sql_parse::Type::Time(_) => BaseType::Time.into(),
223        sql_parse::Type::TinyBlob(_) => BaseType::Bytes.into(),
224        sql_parse::Type::MediumBlob(_) => BaseType::Bytes.into(),
225        sql_parse::Type::Date => BaseType::Date.into(),
226        sql_parse::Type::Blob(_) => BaseType::Bytes.into(),
227        sql_parse::Type::LongBlob(_) => BaseType::Bytes.into(),
228        sql_parse::Type::VarBinary(_) => BaseType::Bytes.into(),
229        sql_parse::Type::Binary(_) => BaseType::Bytes.into(),
230        sql_parse::Type::Boolean => BaseType::Bool.into(),
231        sql_parse::Type::Integer(_) => BaseType::Integer.into(),
232        sql_parse::Type::Float8 => BaseType::Float.into(),
233        sql_parse::Type::Numeric(_, _, _) => todo!("Numeric"),
234        sql_parse::Type::Timestamptz => BaseType::TimeStamp.into(),
235        sql_parse::Type::Json => BaseType::String.into(),
236        sql_parse::Type::Bit(_, _) => BaseType::Bytes.into(),
237        sql_parse::Type::Bytea => BaseType::Bytes.into(),
238        sql_parse::Type::Named(_) => BaseType::String.into(), // TODO lookup name??
239        sql_parse::Type::Inet4 => BaseType::String.into(),
240        sql_parse::Type::Inet6 => BaseType::String.into(),
241    };
242
243    Column {
244        identifier,
245        type_: FullType {
246            t: type_,
247            not_null,
248            list_hack: false,
249        },
250        auto_increment,
251        as_: _as,
252        default,
253    }
254}
255
256/// Parse a schema definition and return a terse description
257///
258/// Errors and warnings are added to issues. The schema is successfully
259/// parsed if no errors are added to issues.
260///
261/// The schema definition in srs should be a sequence of the following
262/// statements:
263/// - Drop table
264/// - Drop function
265/// - Drop view
266/// - Drop procedure
267/// - Create table
268/// - Create function
269/// - Create view
270/// - Create procedure
271/// - Alter table
272pub fn parse_schemas<'a>(
273    src: &'a str,
274    issues: &mut Issues<'a>,
275    options: &TypeOptions,
276) -> Schemas<'a> {
277    let statements = parse_statements(src, issues, &options.parse_options);
278
279    let mut schemas = Schemas {
280        schemas: Default::default(),
281        procedures: Default::default(),
282        functions: Default::default(),
283        indices: Default::default(),
284    };
285
286    for statement in statements {
287        match statement {
288            sql_parse::Statement::CreateTable(t) => {
289                let mut replace = false;
290
291                let id = unqualified_name(issues, &t.identifier);
292
293                let mut schema = Schema {
294                    view: false,
295                    identifier_span: id.span.clone(),
296                    columns: Default::default(),
297                };
298
299                for o in t.create_options {
300                    match o {
301                        sql_parse::CreateOption::OrReplace(_) => {
302                            replace = true;
303                        }
304                        sql_parse::CreateOption::Temporary(s) => {
305                            issues.err("Not supported", &s);
306                        }
307                        sql_parse::CreateOption::Unique(s) => {
308                            issues.err("Not supported", &s);
309                        }
310                        sql_parse::CreateOption::Algorithm(_, _) => {}
311                        sql_parse::CreateOption::Definer { .. } => {}
312                        sql_parse::CreateOption::SqlSecurityDefiner(_, _) => {}
313                        sql_parse::CreateOption::SqlSecurityUser(_, _) => {}
314                    }
315                }
316                // TODO: do we care about table options
317                for d in t.create_definitions {
318                    match d {
319                        sql_parse::CreateDefinition::ColumnDefinition {
320                            identifier,
321                            data_type,
322                        } => {
323                            let column = parse_column(data_type, identifier.clone(), issues);
324                            if let Some(oc) = schema.get_column(column.identifier.value) {
325                                issues
326                                    .err("Column already defined", &identifier)
327                                    .frag("Defined here", &oc.identifier);
328                            } else {
329                                schema.columns.push(column);
330                            }
331                        }
332                        sql_parse::CreateDefinition::ConstraintDefinition { .. } => {}
333                    }
334                }
335                match schemas.schemas.entry(id.clone()) {
336                    alloc::collections::btree_map::Entry::Occupied(mut e) => {
337                        if replace {
338                            e.insert(schema);
339                        } else if t.if_not_exists.is_none() {
340                            issues
341                                .err("Table already defined", &t.identifier)
342                                .frag("Defined here", &e.get().identifier_span);
343                        }
344                    }
345                    alloc::collections::btree_map::Entry::Vacant(e) => {
346                        e.insert(schema);
347                    }
348                }
349            }
350            sql_parse::Statement::CreateView(v) => {
351                let mut replace = false;
352                let mut schema = Schema {
353                    view: true,
354                    identifier_span: v.name.span(),
355                    columns: Default::default(),
356                };
357                for o in v.create_options {
358                    match o {
359                        sql_parse::CreateOption::OrReplace(_) => {
360                            replace = true;
361                        }
362                        sql_parse::CreateOption::Temporary(s) => {
363                            issues.err("Not supported", &s);
364                        }
365                        sql_parse::CreateOption::Unique(s) => {
366                            issues.err("Not supported", &s);
367                        }
368                        sql_parse::CreateOption::Algorithm(_, _) => {}
369                        sql_parse::CreateOption::Definer { .. } => {}
370                        sql_parse::CreateOption::SqlSecurityDefiner(_, _) => {}
371                        sql_parse::CreateOption::SqlSecurityUser(_, _) => {}
372                    }
373                }
374
375                {
376                    let mut typer: crate::typer::Typer<'a, '_> = crate::typer::Typer {
377                        schemas: &schemas,
378                        issues,
379                        reference_types: Vec::new(),
380                        arg_types: Default::default(),
381                        options,
382                        with_schemas: Default::default(),
383                    };
384
385                    let t = type_statement::type_statement(&mut typer, &v.select);
386                    let s = if let type_statement::InnerStatementType::Select(s) = t {
387                        s
388                    } else {
389                        issues.err("Not supported", &v.select.span());
390                        continue;
391                    };
392
393                    for column in s.columns {
394                        //let column: crate::SelectTypeColumn<'a> = column;
395                        let name = column.name.unwrap();
396
397                        schema.columns.push(Column {
398                            identifier: name,
399                            type_: column.type_,
400                            auto_increment: false,
401                            default: false,
402                            as_: None,
403                        });
404                    }
405                }
406
407                match schemas
408                    .schemas
409                    .entry(unqualified_name(issues, &v.name).clone())
410                {
411                    alloc::collections::btree_map::Entry::Occupied(mut e) => {
412                        if replace {
413                            e.insert(schema);
414                        } else if v.if_not_exists.is_none() {
415                            issues
416                                .err("View already defined", &v.name)
417                                .frag("Defined here", &e.get().identifier_span);
418                        }
419                    }
420                    alloc::collections::btree_map::Entry::Vacant(e) => {
421                        e.insert(schema);
422                    }
423                }
424            }
425            sql_parse::Statement::CreateTrigger(_) => {}
426            // sql_parse::Statement::CreateFunction(_) => todo!(),
427            // sql_parse::Statement::Select(_) => todo!(),
428            // sql_parse::Statement::Delete(_) => todo!(),
429            // sql_parse::Statement::Insert(_) => todo!(),
430            // sql_parse::Statement::Update(_) => todo!(),
431            sql_parse::Statement::DropTable(t) => {
432                for i in t.tables {
433                    match schemas.schemas.entry(unqualified_name(issues, &i).clone()) {
434                        alloc::collections::btree_map::Entry::Occupied(e) => {
435                            if e.get().view {
436                                issues
437                                    .err("Name defines a view not a table", &i)
438                                    .frag("View defined here", &e.get().identifier_span);
439                            } else {
440                                e.remove();
441                            }
442                        }
443                        alloc::collections::btree_map::Entry::Vacant(_) => {
444                            if t.if_exists.is_none() {
445                                issues.err("A table with this name does not exist to drop", &i);
446                            }
447                        }
448                    }
449                }
450            }
451            sql_parse::Statement::DropFunction(f) => {
452                match schemas
453                    .functions
454                    .entry(unqualified_name(issues, &f.function).clone())
455                {
456                    alloc::collections::btree_map::Entry::Occupied(e) => {
457                        e.remove();
458                    }
459                    alloc::collections::btree_map::Entry::Vacant(_) => {
460                        if f.if_exists.is_none() {
461                            issues.err(
462                                "A function with this name does not exist to drop",
463                                &f.function,
464                            );
465                        }
466                    }
467                }
468            }
469            sql_parse::Statement::DropProcedure(p) => {
470                match schemas
471                    .procedures
472                    .entry(unqualified_name(issues, &p.procedure).clone())
473                {
474                    alloc::collections::btree_map::Entry::Occupied(e) => {
475                        e.remove();
476                    }
477                    alloc::collections::btree_map::Entry::Vacant(_) => {
478                        if p.if_exists.is_none() {
479                            issues.err(
480                                "A procedure with this name does not exist to drop",
481                                &p.procedure,
482                            );
483                        }
484                    }
485                }
486            }
487            //sql_parse::Statement::DropEvent(_) => todo!(),
488            sql_parse::Statement::DropDatabase(_) => {}
489            sql_parse::Statement::DropServer(_) => {}
490            sql_parse::Statement::DropTrigger(_) => {}
491            sql_parse::Statement::DropView(v) => {
492                for i in v.views {
493                    match schemas.schemas.entry(unqualified_name(issues, &i).clone()) {
494                        alloc::collections::btree_map::Entry::Occupied(e) => {
495                            if !e.get().view {
496                                issues
497                                    .err("Name defines a table not a view", &i)
498                                    .frag("Table defined here", &e.get().identifier_span);
499                            } else {
500                                e.remove();
501                            }
502                        }
503                        alloc::collections::btree_map::Entry::Vacant(_) => {
504                            if v.if_exists.is_none() {
505                                issues.err("A view with this name does not exist to drop", &i);
506                            }
507                        }
508                    }
509                }
510            }
511            sql_parse::Statement::Set(_) => {}
512            sql_parse::Statement::AlterTable(a) => {
513                let e = match schemas
514                    .schemas
515                    .entry(unqualified_name(issues, &a.table).clone())
516                {
517                    alloc::collections::btree_map::Entry::Occupied(e) => {
518                        let e = e.into_mut();
519                        if e.view {
520                            issues.err("Cannot alter view", &a.table);
521                            continue;
522                        }
523                        e
524                    }
525                    alloc::collections::btree_map::Entry::Vacant(_) => {
526                        if a.if_exists.is_none() {
527                            issues.err("Table not found", &a.table);
528                        }
529                        continue;
530                    }
531                };
532                for s in a.alter_specifications {
533                    match s {
534                        sql_parse::AlterSpecification::AddIndex {
535                            if_not_exists,
536                            name,
537                            cols,
538                            ..
539                        } => {
540                            for col in &cols {
541                                if e.get_column(&col.name).is_none() {
542                                    issues
543                                        .err("No such column in table", col)
544                                        .frag("Table defined here", &a.table);
545                                }
546                            }
547
548                            if let Some(name) = &name {
549                                let ident = if options.parse_options.get_dialect().is_postgresql() {
550                                    IndexKey {
551                                        table: None,
552                                        index: name.clone(),
553                                    }
554                                } else {
555                                    IndexKey {
556                                        table: Some(unqualified_name(issues, &a.table).clone()),
557                                        index: name.clone(),
558                                    }
559                                };
560
561                                if let Some(old) = schemas.indices.insert(ident, name.span()) {
562                                    if if_not_exists.is_none() {
563                                        issues
564                                            .err(
565                                                "Multiple indeces with the same identifier",
566                                                &name.span(),
567                                            )
568                                            .frag("Already defined here", &old);
569                                    }
570                                }
571                            }
572                        }
573                        sql_parse::AlterSpecification::AddForeignKey { .. } => {}
574                        sql_parse::AlterSpecification::Modify {
575                            if_exists,
576                            col,
577                            definition,
578                            ..
579                        } => {
580                            let c = match e.get_column_mut(col.value) {
581                                Some(v) => v,
582                                None => {
583                                    if if_exists.is_none() {
584                                        issues
585                                            .err("No such column in table", &col)
586                                            .frag("Table defined here", &e.identifier_span);
587                                    }
588                                    continue;
589                                }
590                            };
591                            *c = parse_column(definition, c.identifier.clone(), issues);
592                        }
593                        sql_parse::AlterSpecification::AddColumn {
594                            identifier,
595                            data_type,
596                            ..
597                        } => {
598                            e.columns.push(parse_column(data_type, identifier, issues));
599                        }
600                        sql_parse::AlterSpecification::OwnerTo { .. } => {}
601                        sql_parse::AlterSpecification::DropColumn { column, .. } => {
602                            let cnt = e.columns.len();
603                            e.columns.retain(|c| c.identifier != column);
604                            if cnt == e.columns.len() {
605                                issues
606                                    .err("No such column in table", &column)
607                                    .frag("Table defined here", &e.identifier_span);
608                            }
609                        }
610                        sql_parse::AlterSpecification::AlterColumn {
611                            column,
612                            alter_column_action,
613                            ..
614                        } => {
615                            let c = match e.get_column_mut(&column.value) {
616                                Some(v) => v,
617                                None => {
618                                    issues
619                                        .err("No such column in table", &column)
620                                        .frag("Table defined here", &e.identifier_span);
621                                    continue;
622                                }
623                            };
624                            match alter_column_action {
625                                sql_parse::AlterColumnAction::SetDefault { .. } => (),
626                                sql_parse::AlterColumnAction::DropDefault { .. } => (),
627                                sql_parse::AlterColumnAction::Type { type_, .. } => {
628                                    *c = parse_column(type_, column, issues)
629                                }
630                                sql_parse::AlterColumnAction::SetNotNull { .. } => {
631                                    c.type_.not_null = true
632                                }
633                                sql_parse::AlterColumnAction::DropNotNull { .. } => {
634                                    c.type_.not_null = false
635                                }
636                            }
637                        }
638                    }
639                }
640            }
641            sql_parse::Statement::Do(_) => {
642                //todo!()
643            }
644            // sql_parse::Statement::Block(_) => todo!(),
645            // sql_parse::Statement::If(_) => todo!(),
646            // sql_parse::Statement::Invalid => todo!(),
647            // sql_parse::Statement::Union(_) => todo!(),
648            // sql_parse::Statement::Replace(_) => todo!(),
649            // sql_parse::Statement::Case(_) => todo!(),
650            sql_parse::Statement::CreateIndex(ci) => {
651                let t = unqualified_name(issues, &ci.table_name);
652
653                if let Some(table) = schemas.schemas.get(t) {
654                    for col in &ci.column_names {
655                        if table.get_column(col).is_none() {
656                            issues
657                                .err("No such column in table", col)
658                                .frag("Table defined here", &table.identifier_span);
659                        }
660                    }
661                    // TODO type where_
662                } else {
663                    issues.err("No such table", &ci.table_name);
664                }
665
666                let ident = if options.parse_options.get_dialect().is_postgresql() {
667                    IndexKey {
668                        table: None,
669                        index: ci.index_name.clone(),
670                    }
671                } else {
672                    IndexKey {
673                        table: Some(t.clone()),
674                        index: ci.index_name.clone(),
675                    }
676                };
677
678                if let Some(old) = schemas.indices.insert(ident, ci.span()) {
679                    if ci.if_not_exists.is_none() {
680                        issues
681                            .err("Multiple indeces with the same identifier", &ci)
682                            .frag("Already defined here", &old);
683                    }
684                }
685            }
686            sql_parse::Statement::DropIndex(ci) => {
687                let key = IndexKey {
688                    table: ci.on.as_ref().map(|(_, t)| t.identifier.clone()),
689                    index: ci.index_name.clone(),
690                };
691                if schemas.indices.remove(&key).is_none() && ci.if_exists.is_none() {
692                    issues.err("No such index", &ci);
693                }
694            }
695            sql_parse::Statement::Commit(_) => (),
696            sql_parse::Statement::Begin(_) => (),
697            sql_parse::Statement::CreateFunction(_) => (),
698            s => {
699                issues.err(
700                    alloc::format!("Unsupported statement {:?} in schema definition", s),
701                    &s,
702                );
703            }
704        }
705    }
706
707    let dummy_schemas = Schemas::default();
708
709    let mut typer = crate::typer::Typer {
710        schemas: &dummy_schemas,
711        issues,
712        reference_types: Vec::new(),
713        arg_types: Default::default(),
714        options,
715        with_schemas: Default::default(),
716    };
717
718    // Compute nullity of generated columns
719    for (name, schema) in &mut schemas.schemas {
720        if schema.columns.iter().all(|v| v.as_.is_none()) {
721            continue;
722        }
723        typer.reference_types.clear();
724        let mut columns = Vec::new();
725        for c in &schema.columns {
726            columns.push((c.identifier.clone(), c.type_.clone()));
727        }
728        typer.reference_types.push(crate::typer::ReferenceType {
729            name: Some(name.clone()),
730            span: schema.identifier_span.clone(),
731            columns,
732        });
733        for c in &mut schema.columns {
734            if let Some(as_) = &c.as_ {
735                let full_type = crate::type_expression::type_expression(
736                    &mut typer,
737                    as_,
738                    crate::type_expression::ExpressionFlags::default(),
739                    BaseType::Any,
740                );
741                c.type_.not_null = full_type.not_null;
742            }
743        }
744    }
745    schemas
746}