Skip to main content

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