mirror of https://github.com/AMT-Cheif/drift.git
147 lines
4.6 KiB
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';
|
|
}
|
|
}
|