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