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