drift/sqlparser/lib/src/analysis/schema/from_create_table.dart

147 lines
4.6 KiB
Dart

part of '../analysis.dart';
/// Reads the [Table] definition from a [CreateTableStatement].
class SchemaFromCreateTable {
/// Whether we should provide additional type hints for nonstandard `BOOL`
/// and `DATETIME` columns.
final bool moorExtensions;
const SchemaFromCreateTable({this.moorExtensions = false});
/// Reads a [Table] schema from the [stmt] inducing a table (either a
/// [CreateTableStatement] or a [CreateVirtualTableStatement]).
///
/// This method might throw an exception if the table could not be read.
Table read(TableInducingStatement stmt) {
if (stmt is CreateTableStatement) {
return _readCreateTable(stmt);
} else if (stmt is CreateVirtualTableStatement) {
final module = stmt.scope.resolve<Module>(stmt.moduleName);
if (module == null) {
throw CantReadSchemaException('Unknown module "${stmt.moduleName}", '
'did you register it?');
}
return module.parseTable(stmt);
}
throw AssertionError('Unknown table statement');
}
/// Creates a [View] from a [CreateViewStatement]. The `CREATE VIEW` statement
/// must be fully resolved through [context] when calling this method.
///
/// Example:
/// ```dart
/// // this will run analysis on the inner select statement and resolve columns
/// final ctx = engine.analyze('CREATE VIEW ...');
/// final createViewStmt = ctx.root as CreateViewStatement;
///
/// final view = const SchemaFromCreateTable().readView(ctx, createViewStmt);
/// ```
View readView(AnalysisContext context, CreateViewStatement stmt) {
final columnsFromSelect = stmt.query.resolvedColumns;
final overriddenNames = stmt.columns ?? const [];
final viewColumns = List<ViewColumn>(columnsFromSelect.length);
for (var i = 0; i < columnsFromSelect.length; i++) {
final column = columnsFromSelect[i];
// overriddenNames might be shorter than the columns. That's not a valid
// CREATE VIEW statement, but we try not to crash.
final name = i < overriddenNames.length ? overriddenNames[i] : null;
viewColumns[i] = ViewColumn(column, context.typeOf(column).type, name);
}
return View(
name: stmt.viewName,
resolvedColumns: viewColumns,
definition: stmt,
);
}
Table _readCreateTable(CreateTableStatement stmt) {
return Table(
name: stmt.tableName,
resolvedColumns: [for (var def in stmt.columns) _readColumn(def)],
withoutRowId: stmt.withoutRowId,
tableConstraints: stmt.tableConstraints,
definition: stmt,
);
}
TableColumn _readColumn(ColumnDefinition definition) {
final type = resolveColumnType(definition.typeName);
final nullable = !definition.constraints.any((c) => c is NotNull);
final resolvedType = type.withNullable(nullable);
return TableColumn(
definition.columnName,
resolvedType,
definition: definition,
);
}
/// Resolves a column type via its typename, see the linked rules below.
/// Additionally, if [moorExtensions] are enabled, we support [IsBoolean] and
/// [IsDateTime] hints if the type name contains `BOOL` or `DATE`,
/// respectively.
/// https://www.sqlite.org/datatype3.html#determination_of_column_affinity
ResolvedType resolveColumnType(String /*?*/ typeName) {
if (typeName == null) {
return const ResolvedType(type: BasicType.blob);
}
final upper = typeName.toUpperCase();
if (upper.contains('INT')) {
return const ResolvedType(type: BasicType.int);
}
if (upper.contains('CHAR') ||
upper.contains('CLOB') ||
upper.contains('TEXT')) {
return const ResolvedType(type: BasicType.text);
}
if (upper.contains('BLOB')) {
return const ResolvedType(type: BasicType.blob);
}
if (moorExtensions) {
if (upper.contains('BOOL')) {
return const ResolvedType.bool();
}
if (upper.contains('DATE')) {
return const ResolvedType(type: BasicType.int, hint: IsDateTime());
}
if (upper.contains('ENUM')) {
return const ResolvedType(type: BasicType.int);
}
}
return const ResolvedType(type: BasicType.real);
}
/// Looks up the correct column affinity for a declared type name with the
/// rules described here:
/// https://www.sqlite.org/datatype3.html#determination_of_column_affinity
@visibleForTesting
BasicType columnAffinity(String typeName) => resolveColumnType(typeName).type;
}
/// Thrown when a table schema could not be read.
class CantReadSchemaException implements Exception {
final String message;
CantReadSchemaException(this.message);
@override
String toString() {
return 'Could not read table schema: $message';
}
}