part of 'dsl.dart'; /// Base class for dsl [Table]s and [View]s. abstract class HasResultSet { /// Default constant constructor. const HasResultSet(); } /// Subclasses represent a table in a database generated by drift. /// /// For more information on how to write tables, see [the documentation](https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/) abstract class Table extends HasResultSet { /// Defines a table to be used with drift. const Table(); /// The sql table name to be used. By default, drift will use the snake_case /// representation of your class name as the sql table name. For instance, a /// [Table] class named `LocalSettings` will be called `local_settings` by /// default. /// You can change that behavior by overriding this method to use a custom /// name. Please note that you must directly return a string literal by using /// a getter. For instance `@override String get tableName => 'my_table';` is /// valid, whereas `@override final String tableName = 'my_table';` or /// `@override String get tableName => createMyTableName();` is not. @visibleForOverriding String? get tableName => null; /// Whether to append a `WITHOUT ROWID` clause in the `CREATE TABLE` /// statement. This is intended to be used by generated code only. bool get withoutRowId => false; /// Drift will write some table constraints automatically, for instance when /// you override [primaryKey]. You can turn this behavior off if you want to. /// This is intended to be used by generated code only. bool get dontWriteConstraints => false; /// Whether this table is `STRICT`. /// /// Strict tables enforce stronger type constraints for inserts and updates. /// Support for strict tables was added in sqlite3 version 37. /// This field is intended to be used by generated code only. bool get isStrict => false; /// Override this to specify custom primary keys: /// ```dart /// class IngredientInRecipes extends Table { /// @override /// Set get primaryKey => {recipe, ingredient}; /// /// IntColumn get recipe => integer()(); /// IntColumn get ingredient => integer()(); /// /// IntColumn get amountInGrams => integer().named('amount')(); ///} /// ``` /// The getter must return a set literal using the `=>` syntax so that the /// drift generator can understand the code. /// Also, please note that it's an error to have an /// [BuildIntColumn.autoIncrement] column and a custom primary key. /// As an auto-incremented `IntColumn` is recognized by drift to be the /// primary key, doing so will result in an exception thrown at runtime. @visibleForOverriding Set? get primaryKey => null; /// Unique constraints in this table. /// /// When two rows have the same value in _any_ set specified in [uniqueKeys], /// the database will reject the second row for inserts. /// /// Override this to specify unique keys: /// /// ```dart /// class IngredientInRecipes extends Table { /// @override /// List> get uniqueKeys => /// [{recipe, ingredient}, {recipe, amountInGrams}]; /// /// IntColumn get recipe => integer()(); /// IntColumn get ingredient => integer()(); /// /// IntColumn get amountInGrams => integer().named('amount')(); /// ``` /// /// The getter must return a list of set literals using the `=>` syntax so /// that the drift generator can understand the code. /// /// Note that individual columns can also be marked as unique with /// [BuildGeneralColumn.unique]. This is equivalent to adding a single-element /// set to this list. @visibleForOverriding List>? get uniqueKeys => null; /// Custom table constraints that should be added to the table. /// /// See also: /// - https://www.sqlite.org/syntax/table-constraint.html, which defines what /// table constraints are supported. List get customConstraints => []; /// Use this as the body of a getter to declare a column that holds integers. /// /// Example (inside the body of a table class): /// ``` /// IntColumn get id => integer().autoIncrement()(); /// ``` /// /// In sqlite3, an integer column stores 64-big integers. This column maps /// values to an [int] in Dart, which works well on native platforms. On the /// web, be aware that [int]s are [double]s internally which means that only /// integers smaller than 2⁵² can safely be stored. /// If you need web support __and__ a column that potential stores integers /// larger than what fits into 52 bits, consider using a [int64] column /// instead. That column stores the same value in a database, but makes drift /// report the values as a [BigInt] in Dart. @protected ColumnBuilder integer() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds a 64-big /// integer as a [BigInt]. /// /// The main purpose of this column is to support large integers for web apps /// compiled to JavaScript, where using an [int] does not reliably work for /// numbers larger than 2⁵². /// It stores the exact same data as an [integer] column (and supports the /// same options), but instructs drift to generate a data class with a /// [BigInt] field and a database conversion aware of large intergers. /// /// __Note__: The use of [int64] is only necessary for apps that need to work /// on the web __and__ use columns that are likely to store values larger than /// 2⁵². In all other cases, using [integer] directly is much more efficient /// and recommended. @protected ColumnBuilder int64() => _isGenerated(); /// Creates a column to store an `enum` class [T]. /// /// In the database, the column will be represented as an integer /// corresponding to the enum's index. Note that this can invalidate your data /// if you add another value to the enum class. @protected ColumnBuilder intEnum() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds strings. /// Example (inside the body of a table class): /// ``` /// TextColumn get name => text()(); /// ``` @protected ColumnBuilder text() => _isGenerated(); /// Creates a column to store an `enum` class [T]. /// /// In the database, the column will be represented as text corresponding to /// the name of the enum entries. Note that this can invalidate your data if /// you rename the entries of the enum class. ColumnBuilder textEnum() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds bools. /// Example (inside the body of a table class): /// ``` /// BoolColumn get isAwesome => boolean()(); /// ``` @protected ColumnBuilder boolean() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds date and /// time values. /// /// Drift supports two modes for storing date times: As unix timestamp with /// second accuracy (the default) and as ISO 8601 string with microsecond /// accuracy. For more information between the modes, and information on how /// to change them, see [the documentation]. /// /// Note that [DateTime] values are stored on a second-accuracy. /// Example (inside the body of a table class): /// ``` /// DateTimeColumn get accountCreatedAt => dateTime()(); /// ``` /// /// [dateTime] columns are optimized for SQLite. When using drift with another /// database, such as PostgreSQL, use [native datetime columns](https://drift.simonbinder.eu/platforms/postgres/#avoiding-sqlite-specific-drift-apis). /// /// [the documentation]: https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#supported-column-types @protected ColumnBuilder dateTime() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds arbitrary /// data blobs, stored as an [Uint8List]. Example: /// ``` /// BlobColumn get payload => blob()(); /// ``` @protected ColumnBuilder blob() => _isGenerated(); /// Use this as the body of a getter to declare a column that holds floating /// point numbers. Example /// ``` /// RealColumn get averageSpeed => real()(); /// ``` @protected ColumnBuilder real() => _isGenerated(); /// Use this as a the body of a getter to declare a column that holds /// arbitrary values not modified by drift at runtime. /// /// The type of this column in the schema is `ANY`, which is particularly /// useful for columns with an unknown type in [isStrict] tables. /// This type has no direct equivalent for other database engines. @protected ColumnBuilder sqliteAny() => _isGenerated(); /// Defines a column with a custom [type] when used as a getter. /// /// For more information on custom types and when they can be useful, see /// https://drift.simonbinder.eu/docs/sql-api/types/. /// /// For most users, [TypeConverter]s are a more appropriate tool to store /// custom values in the database. @protected ColumnBuilder customType(UserDefinedSqlType type) => _isGenerated(); } /// Subclasses represent a view in a database generated by drift. /// /// For more information on how to define views in Dart, see /// [the documentation](https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#views) abstract class View extends HasResultSet { /// Defines a view to be used with drift. const View(); /// The select method can be used in [as] to define the select query backing /// this view. /// /// The select statement should select all columns defined on this view. @protected View select(List columns) => _isGenerated(); /// This method should be called on [select] to define the main table of this /// view: /// /// ```dart /// abstract class CategoryTodoCount extends View { /// TodosTable get todos; /// Categories get categories; /// /// Expression get itemCount => todos.id.count(); /// /// @override /// Query as() => select([categories.description, itemCount]) /// .from(categories) /// .join([innerJoin(todos, todos.category.equalsExp(categories.id))]); /// } /// ``` @protected JoinedSelectStatement from(Table table) => _isGenerated(); /// This method is overridden by Dart-defined views to declare the right /// query to run. @visibleForOverriding Query as(); } /// Annotations for Dart table classes to define a [SQL index](https://sqlite.org/lang_createindex.html) /// to add to the table. /// /// ```dart /// @TableIndex(name: 'item_title', columns: {#title}) /// class TodoItems extends Table { /// IntColumn get id => integer().autoIncrement()(); /// TextColumn get title => text()(); /// TextColumn get content => text().nullable()(); /// } /// ``` @Target({TargetKind.classType}) final class TableIndex { /// The name of the index in SQL. /// /// Please note that the name of every table, view, index and other elements /// in a database must be unique. For this reason, the names of indices are /// commonly prefixed with the name of the table they're referencing. final String name; /// Whether this index is `UNIQUE`, meaning that the database will forbid /// multiple rows in the annotated table from having the same values in the /// indexed columns. final bool unique; /// The columns of the table that should be part of the index. /// /// This can either be a [Symbol] for the default options or an /// [IndexedColumn] instance. /// /// Columns are referenced with a [Symbol] of their getter name used in the /// column definition. For instance, a table declaring a column as /// `IntColumn get nextUpdateSnapshot => ...()` could reference this column /// using `#nextUpdateSnapshot`. /// /// To further control options of the index, use an [IndexedColumn] instance. final Set columns; /// As an alternative to [name], [unique] and [columns], a `CREATE INDEX` SQL /// statement defining the index. /// /// `drift_dev` will parse and validate the statement at build-time. final String? createIndexStatement; /// An annotation for Dart-defined drift tables telling drift to add an SQL /// index to the table. /// /// See the class documentation at [TableIndex] for an example. const TableIndex({ required this.name, required this.columns, this.unique = false, }) : createIndexStatement = null; /// An annotation for Dart-defined drift tables telling drift to add an index /// defined by a [createIndexStatement]. /// /// The index is still validated by `drift_dev` at build time. Using a custom /// SQL statement enables advanced index options, such as using custom /// collations or indexing expressions. It can also be used for partials /// indexes by adding a `WHERE` clause. const TableIndex.sql(String this.createIndexStatement) : name = '', unique = false, columns = const {}; } /// A column that can appear in a [TableIndex]. final class IndexedColumn { /// The columns of the table that should be part of the index. /// /// Columns are referenced with a [Symbol] of their getter name used in the /// column definition. For instance, a table declaring a column as /// `IntColumn get nextUpdateSnapshot => ...()` could reference this column /// using `#nextUpdateSnapshot`. final Symbol columnName; /// The [OrderingMode] to use for the index, controlling the order of values /// in the b-tree. final OrderingMode? orderBy; /// Creates an indexed column from a [Symbol] and index options. const IndexedColumn(this.columnName, {required this.orderBy}); } /// A class to be used as an annotation on [Table] classes to customize the /// name for the data class that will be generated for the table class. The data /// class is a dart object that will be used to represent a row in the table. /// /// {@template drift_custom_data_class} /// By default, drift will attempt to use the singular form of the table name /// when naming data classes (e.g. a table named "Users" will generate a data /// class called "User"). However, this doesn't work for irregular plurals and /// you might want to choose a different name, for which this annotation can be /// used. /// {@endtemplate} @Target({TargetKind.classType}) class DataClassName { /// The overridden name to use when generating the data class for a table. /// {@macro drift_custom_data_class} final String? name; /// The overridden name to use when generating the companion class for a table. final String? companion; /// The parent type of the data class generated by drift. /// /// The [extending] type must refer to an interface type (usually just a /// class name), and the parent class must extend [DataClass]. /// /// The extended class can optionally have a type parameter, which is /// instantiated to the actual data class generated by drift. /// /// For example, /// /// ```dart /// abstract class BaseModel extends DataClass { /// abstract final String id; /// } /// /// abstract class TypedBaseModel extends DataClass { /// /// } /// /// @DataClassName('Company', extending: BaseModel) /// class Companies extends Table { /// TextColumn get id => text()(); /// TextColumn get name => text().named('name')(); /// } /// /// // The actual generated class will extend `TypedBaseModel`. /// @DataClassName('Employee', extending: TypedBaseModel) /// class Employees extends Table { /// TextColumn get id => text()(); /// } /// ``` final Type? extending; /// A list of classes that the drift-generated row class should implement. /// /// Listing classes here can be useful when you have several tables with the /// same columns, as it allows extracting them into common interfaces shared /// between multiple row classes: /// /// ```dart /// abstract interface class HasCreationTimes { /// DateTime get createdAt; /// } /// /// @DataClassName.custom(implementing: [HasCreationTimes]) /// class Accounts extends Table { /// // ... /// DateTimeColumn get createdAt => dateTime() /// .withDefault(currentDateAndTime)(); /// } /// ``` final List? implementing; /// Customize the data class name for a given table. /// {@macro drift_custom_data_class} const DataClassName( this.name, { this.extending, this.implementing, this.companion, }); /// Customize the data class name for a given table. /// {@macro drift_custom_data_class} const DataClassName.custom({ this.name, this.extending, this.implementing, this.companion, }); } /// An annotation specifying an existing class to be used as a data class. /// /// By default, drift generates a row class as a typed representation of a row /// in the table classes you define. /// If you want to, you can replace this row class with your own structure by /// applying [UseRowClass] on the table: /// /// ```dart /// @UseRowClass(User) /// class Users extends Table { /// IntColumn get id => integer().autoIncrement()(); /// TextColumn get name => text()(); /// } /// /// final class User { /// final int id; /// final String name; /// /// User(this.id, this.name); /// } /// ``` /// /// The associated row class must have a constructor "compatible" with the /// columns from the table (meaning that each parameter on the constructor /// matches a column from the table by name and type). Not all columns present /// in the table need to be added to the row class, drift will simply ignore the /// others. Since drift constructs the row class from a table row however, the /// constructor must not have parameters not present as table columns. /// /// Instead of an existing class, you can also use [Record] or a record type /// through a typedef as a type to use for rows: /// /// ```dart /// typedef User = ({int id, String name}); /// /// @UseRowClass(User) /// class Users extends Table { /// ``` /// /// If you want to use instances of your custom row classes as sources for /// inserts or update statements, you can enable the `write_to_columns_mixins` // ignore: deprecated_member_use_from_same_package /// builder option or set [generateInsertable] to true. It will make drift /// generate an extension on the row type to return a companion: /// /// ```dart /// @UseRowClass(User, generateInsertable: true) /// class Users extends Table { /// IntColumn get id => integer().autoIncrement()(); /// TextColumn get name => text()(); /// } /// /// final class User implements Insertable { /// final int id; /// final String name; /// /// User(this.id, this.name); /// /// @override /// Map toColumns(bool nullToAbsent) { /// return toInsertable().toColumns(nullToAbsent); /// } /// } /// ``` /// /// For more details, see the [documentation page](https://drift.simonbinder.eu/dart_api/rows/#custom-dataclass). @Target({TargetKind.classType}) class UseRowClass { /// The existing class /// /// This type must refer to an existing class or a record structure. All other /// types, like functions or types with arguments, are not allowed. final Type type; /// The name of the constructor to use. /// /// When this option is not set, the default (unnamed) constructor will be /// used to map database rows to the desired row class. final String constructor; /// Generate a `toInsertable()` extension function for [type] mapping all /// fields to an insertable object. /// /// This can be useful when a custom data class should be used for inserts or /// updates. @Deprecated('Use `write_to_columns_mixins` build option instead') final bool generateInsertable; /// Customize the class used by drift to hold an instance of an annotated /// table. /// /// For details, see the class documentation on [UseRowClass]. const UseRowClass(this.type, {this.constructor = 'new', this.generateInsertable = false}); } /// An annotation specifying view properties @Target({TargetKind.classType}) class DriftView { /// The sql view name to be used. By default, drift will use the snake_case /// representation of your class name as the sql view name. For instance, a /// [View] class named `UserView` will be called `user_view` by /// default. final String? name; /// The name for the data class that will be generated for the view class. /// The data class is a dart object that will be used to represent a result of /// the view. /// {@template drift_custom_data_class} /// By default, drift will attempt to use the view name followed by "Data" /// when naming data classes (e.g. a view named "UserView" will generate a /// data class called "UserViewData"). /// {@endtemplate} final String? dataClassName; /// The parent class of generated data class. Class must extends [DataClass]! final Type? extending; /// Customize view name and data class name const DriftView({this.name, this.dataClassName, this.extending}); }