mirror of https://github.com/AMT-Cheif/drift.git
220 lines
6.6 KiB
Dart
220 lines
6.6 KiB
Dart
part of '../analysis.dart';
|
|
|
|
/// A column that appears in a [ResultSet]. Has a type and a name.
|
|
abstract class Column
|
|
with Referencable, HasMetaMixin
|
|
implements Typeable, HumanReadable {
|
|
/// The name of this column in the result set.
|
|
String get name;
|
|
|
|
/// Whether this column is included in results when running a select query
|
|
/// like `SELECT * FROM table`.
|
|
///
|
|
/// Some columns, notably the rowid aliases, are exempt from this.
|
|
bool get includedInResults => true;
|
|
|
|
@override
|
|
String humanReadableDescription() {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
/// A column that is part of a table.
|
|
class TableColumn extends Column {
|
|
@override
|
|
final String name;
|
|
|
|
/// The type of this column, which is available before any resolution happens
|
|
/// (we know ii from the table).
|
|
ResolvedType get type => _type;
|
|
ResolvedType _type;
|
|
|
|
/// The column constraints set on this column.
|
|
///
|
|
/// This only works columns where [hasDefinition] is true, otherwise this
|
|
/// getter will throw. The columns in a `CREATE TABLE` statement always have
|
|
/// a definition, but those from a `CREATE VIRTUAL TABLE` likely don't.
|
|
///
|
|
/// See also:
|
|
/// - https://www.sqlite.org/syntax/column-constraint.html
|
|
List<ColumnConstraint> get constraints => definition.constraints;
|
|
|
|
/// The definition in the AST that was used to create this column model.
|
|
final ColumnDefinition definition;
|
|
|
|
/// Whether this column has a definition from the ast.
|
|
bool get hasDefinition => definition != null;
|
|
|
|
/// The table this column belongs to.
|
|
Table table;
|
|
|
|
TableColumn(this.name, this._type, {this.definition});
|
|
|
|
/// Applies a type hint to this column.
|
|
///
|
|
/// The [hint] will then be reflected in the [type].
|
|
void applyTypeHint(TypeHint hint) {
|
|
_type = _type?.copyWith(hint: hint);
|
|
}
|
|
|
|
/// Whether this column is an alias for the rowid, as defined in
|
|
/// https://www.sqlite.org/lang_createtable.html#rowid
|
|
///
|
|
/// To summarize, a column is an alias for the rowid if all of the following
|
|
/// conditions are met:
|
|
/// - the table has a primary key that consists of exactly one (this) column
|
|
/// - the column is declared to be an integer
|
|
/// - if this column has a [PrimaryKeyColumn], the [OrderingMode] of that
|
|
/// constraint is not [OrderingMode.descending].
|
|
bool isAliasForRowId() {
|
|
if (definition == null ||
|
|
table == null ||
|
|
type?.type != BasicType.int ||
|
|
table.withoutRowId) {
|
|
return false;
|
|
}
|
|
|
|
// We need to check whether this column is a primary key, which could happen
|
|
// because of a table or a column constraint
|
|
final columnsWithKey = table.tableConstraints.whereType<KeyClause>();
|
|
for (final tableConstraint in columnsWithKey) {
|
|
if (!tableConstraint.isPrimaryKey) continue;
|
|
|
|
final columns = tableConstraint.indexedColumns;
|
|
if (columns.length == 1 && columns.single.columnName == name) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// option 2: This column has a primary key constraint
|
|
for (final primaryConstraint in constraints.whereType<PrimaryKeyColumn>()) {
|
|
if (primaryConstraint.mode == OrderingMode.descending) return false;
|
|
|
|
// additional restriction: Column type must be exactly "INTEGER"
|
|
return definition.typeName == 'INTEGER';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
String humanReadableDescription() {
|
|
return '$name in ${table.humanReadableDescription()}';
|
|
}
|
|
}
|
|
|
|
/// A column that is part of a view.
|
|
class ViewColumn extends Column with DelegatedColumn {
|
|
|
|
final String _name;
|
|
|
|
@override
|
|
final Column innerColumn;
|
|
|
|
/// The view this column belongs to.
|
|
View view;
|
|
|
|
/// Creates a view column wrapping a [Column] from the select statement used
|
|
/// to create the view.
|
|
///
|
|
/// The optional name parameter can be used to override the name for this column.
|
|
/// By default, the name of the [innerColumn] will be used.
|
|
ViewColumn(this.innerColumn, [this._name]);
|
|
|
|
@override
|
|
String get name => _name ?? super.name;
|
|
|
|
@override
|
|
String humanReadableDescription() {
|
|
return '$name in ${view.humanReadableDescription()}';
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Refers to the special "rowid", "oid" or "_rowid_" column defined for tables
|
|
/// that weren't created with an `WITHOUT ROWID` clause.
|
|
class RowId extends TableColumn {
|
|
// note that such alias is always called "rowid" in the result set -
|
|
// "SELECT oid FROM table" yields a sinle column called "rowid"
|
|
RowId() : super('rowid', const ResolvedType(type: BasicType.int));
|
|
|
|
@override
|
|
bool get includedInResults => false;
|
|
}
|
|
|
|
/// A column that is created by an expression. For instance, in the select
|
|
/// statement "SELECT 1 + 3", there is a column called "1 + 3" of type int.
|
|
class ExpressionColumn extends Column {
|
|
@override
|
|
final String name;
|
|
|
|
/// The expression returned by this column.
|
|
final Expression expression;
|
|
|
|
ExpressionColumn({@required this.name, this.expression});
|
|
}
|
|
|
|
/// A column that is created by a reference expression. The difference to an
|
|
/// [ExpressionColumn] is that the correct case of the column name depends on
|
|
/// the resolved reference.
|
|
class ReferenceExpressionColumn extends ExpressionColumn {
|
|
Reference get reference => expression as Reference;
|
|
|
|
@override
|
|
String get name => overriddenName ?? reference.resolvedColumn?.name;
|
|
|
|
final String overriddenName;
|
|
|
|
ReferenceExpressionColumn(Reference ref, {this.overriddenName})
|
|
: super(name: null, expression: ref);
|
|
}
|
|
|
|
/// A column that wraps another column.
|
|
mixin DelegatedColumn on Column {
|
|
Column get innerColumn;
|
|
|
|
@override
|
|
String get name => innerColumn.name;
|
|
}
|
|
|
|
/// The result column of a [CompoundSelectStatement].
|
|
class CompoundSelectColumn extends Column with DelegatedColumn {
|
|
/// The column in [CompoundSelectStatement.base] each of the
|
|
/// [CompoundSelectStatement.additional] that contributed to this column.
|
|
final List<Column> columns;
|
|
|
|
CompoundSelectColumn(this.columns);
|
|
|
|
@override
|
|
Column get innerColumn => columns.first;
|
|
}
|
|
|
|
class CommonTableExpressionColumn extends Column with DelegatedColumn {
|
|
@override
|
|
final String name;
|
|
|
|
@override
|
|
Column innerColumn;
|
|
|
|
// note that innerColumn is mutable because the column might not be known
|
|
// during all analysis phases.
|
|
|
|
CommonTableExpressionColumn(this.name, this.innerColumn);
|
|
}
|
|
|
|
/// Result column coming from a `VALUES` select statement.
|
|
class ValuesSelectColumn extends Column {
|
|
@override
|
|
final String name;
|
|
|
|
/// The expressions from a `VALUES` clause contributing to this column.
|
|
///
|
|
/// Essentially, each [ValuesSelectColumn] consists of a column in the values
|
|
/// of a [ValuesSelectStatement].
|
|
final List<Expression> expressions;
|
|
|
|
ValuesSelectColumn(this.name, this.expressions)
|
|
: assert(expressions.isNotEmpty);
|
|
}
|