drift/sqlparser/lib/src/analysis/schema/column.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);
}