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