part of 'dsl.dart'; /// A [KeyAction] can be used on a [BuildColumn.references] clause to describe /// how updates and deletes to a referenced table should propagate in your /// database. /// /// By default, [KeyAction.noAction] will be used. /// For details, see [the sqlite3 documentation](https://www.sqlite.org/foreignkeys.html#fk_actions). enum KeyAction { /// Set the column to null when the referenced column changes. setNull, /// Set the column back to its default value when the referenced column /// changes. setDefault, /// Propagate updates and deletes into referencing rows. cascade, /// Forbid deleting or updating the referenced column in a database if there /// are children pointing towards it. restrict, /// No special action is taken when the parent key is modified or deleted from /// the database. /// /// For [SqlDialect.mariadb] this is synonym for [restrict]. noAction, } /// Base class for columns in sql. Type [T] refers to the type a value of this /// column will have in Dart. abstract class Column extends Expression { @override final Precedence precedence = Precedence.primary; /// The (unescaped) name of this column. /// /// Use [escapedNameFor] to access a name that's escaped in double quotes if /// needed. String get name; /// [name], but wrapped in double quotes to escape it as a a same identifier. /// /// In the past, this getter only used to add double-quotes when that is /// really needed (for instance because [name] is also a reserved keyword). /// For performance reasons, we unconditionally escape names now. @Deprecated('Use escapedNameFor with the current dialect') String get escapedName => '"$name"'; /// [name], but wrapped in double quotes or the DBMS-specific escape /// identifier. String escapedNameFor(SqlDialect dialect) { return dialect.escape(name); } } /// A column that stores int values. typedef IntColumn = Column; /// A column that stores BigInt values. /// /// In a database, this is the exact same thing as an [IntColumn]. However, /// unlike an [IntColumn], the resulting value is mapped to a [BigInt] in Dart. /// As sqlite3 only supports 64-big integers (the same range that an [int] in /// Dart can represent), there is no need to use an [Int64Column] for native /// apps. For Dart compiled to JavaScript though, it may be beneficial to use /// [BigInt]s to avoid loosing precision for values larger than 2⁵². typedef Int64Column = Column; /// A column that stores boolean values. Booleans will be stored as an integer /// that can either be 0 (false) or 1 (true). typedef BoolColumn = Column; /// A column that stores text. typedef TextColumn = Column; /// A column that stores a [DateTime]. Times will be stored as unix timestamp /// and will thus have a second accuracy. typedef DateTimeColumn = Column; /// A column that stores arbitrary blobs of data as a [Uint8List]. typedef BlobColumn = Column; /// A column that stores floating point numeric values. typedef RealColumn = Column; /// A column storing arbitrary values using SQLite's `ANY` type. typedef AnyColumn = Column; class _BaseColumnBuilder {} /// A column builder is used to specify which columns should appear in a table. /// All of the methods defined in this class and its subclasses are not meant to /// be called at runtime. Instead, the generator will take a look at your /// source code (specifically, it will analyze which of the methods you use) to /// figure out the column structure of a table. class ColumnBuilder extends _BaseColumnBuilder {} /// A column builder for virtual, generated columns. /// /// This is a different class so that some methods are not available class VirtualColumnBuilder extends _BaseColumnBuilder {} /// DSL extension to define a column inside a drift table. extension BuildColumn on ColumnBuilder { /// Tells drift to write a custom constraint after this column definition when /// writing this column, for instance in a CREATE TABLE statement. /// /// When no custom constraint is set, columns will be written like this: /// `name TYPE NULLABILITY NATIVE_CONSTRAINTS`. Native constraints are used to /// enforce that booleans are either 0 or 1 (e.g. /// `field BOOLEAN NOT NULL CHECK (field in (0, 1)`). Auto-Increment /// columns also make use of the native constraints, as do default values. /// If [customConstraint] has been called, the nullability information and /// native constraints will never be written. Instead, they will be replaced /// with the [constraint]. For example, if you call /// `customConstraint('UNIQUE')` on an [IntColumn] named "votes", the /// generated column definition will be `votes INTEGER UNIQUE`. Notice how the /// nullability information is lost - you'll have to include it in /// [constraint] if that is desired. /// /// This can be used to implement constraints that drift does not (yet) /// support. If you've found a common use-case for this, it should be /// considered a limitation of drift itself. Please feel /// free to open an issue at https://github.com/simolus3/drift/issues/new to /// report that. /// /// See also: /// - https://www.sqlite.org/syntax/column-constraint.html /// - [GeneratedColumn.$customConstraints] ColumnBuilder customConstraint(String constraint) => _isGenerated(); /// The column will use this expression when a row is inserted and no value /// has been specified. /// /// Note: Unlike most other methods used to declare tables, the parameter /// [e] which denotes the default expression doesn't have to be a Dart /// constant. /// Particularly, you can use operators like those defined in /// [BooleanExpressionOperators] to form expressions here. /// /// If you need a column that just stores a static default value, you could /// use this method with a [Constant]: /// ```dart /// IntColumn get level => int().withDefault(const Constant(1))(); /// ``` /// /// See also: /// - [Constant], which can be used to model literals that appear in CREATE /// TABLE statements. /// - [currentDate] and [currentDateAndTime], which are useful expressions to /// store the current date/time as a default value. ColumnBuilder withDefault(Expression e) => _isGenerated(); /// Sets a dynamic default value for this column. /// /// When a row is inserted into the table and no value has been specified for /// this column, [onInsert] will be evaluated. Its return value will be used /// for the missing column. [onInsert] may return different values when called /// multiple times. /// /// Here's an example using the [uuid](https://pub.dev/packages/uuid) package: /// /// ```dart /// final uuid = Uuid(); /// /// class Pictures extends Table { /// TextColumn get id => text().clientDefault(() => uuid.v4())(); /// BlobColumn get rawData => blob(); /// /// @override /// Set get primaryKey => {id}; /// } /// ``` /// /// For a default value that's constant, it is more efficient to use /// [withDefault] instead. [withDefault] will write the default value into the /// generated `CREATE TABLE` statement. The underlying sql engine will then /// apply the default value. ColumnBuilder clientDefault(T Function() onInsert) => _isGenerated(); /// Adds a foreign-key reference from this column. /// /// The [table] type must be a Dart class name defining a drift table. /// The [column] must be a Dart symbol with the same name as a column in the /// referenced table. /// In Dart, symbols can be created by prefixing an identifier with `#`. /// /// In the following example, a `Books` table keeps a reference to the author /// of each book: /// /// ```dart /// class Authors extends Table { /// IntColumn get id => integer().autoIncrement()(); /// // ... /// } /// /// class Books extends Table { /// IntColumn get author => integer().references(Authors, #id)(); /// } /// ``` /// /// __Important notice__: In sqlite3, foreign keys are not enabled by default. /// When using foreign keys, remember to enable the option in the /// [MigrationStrategy.beforeOpen] callback: /// /// ```dart /// beforeOpen: (details) async { /// await customStatement('PRAGMA foreign_keys = ON'); /// } /// ``` /// /// The optional [onUpdate] and [onDelete] actions can be used to /// automatically propagate changes from the referenced row to this column. /// See [KeyAction] for details. /// /// [initiallyDeferred] can be set to `true` to generate the foreign key as /// `DEFERRABLE INITIALLY DEFERRED`, meaning that it is checked at the end of /// a transaction only instead of after every statement. ColumnBuilder references( Type table, Symbol column, { KeyAction? onUpdate, KeyAction? onDelete, bool initiallyDeferred = false, }) { _isGenerated(); } /// Adds a `CHECK` constraint to this column. /// /// The check, encoded as a boolean [Expression], is evaluated when a new row /// is added into the table or when this column is updated. /// If, at that time, the [condition] evaluates to `false`, the insert or /// update is rejected (it fails with an exception). /// /// ```dart /// // Column that can only be set to times after 1950. /// DateTimeColumn get creationTime => dateTime() /// .check(creationTime.isBiggerThan(Constant(DateTime(1950)))) /// .withDefault(currentDateAndTime)(); /// ``` /// /// Note that the [condition] is written into the database schema (it is /// part of a `CREATE TABLE` statement). As a consequence, changes to the /// [condition] need to be updated in the database schema with an explicit /// [schema migration](https://drift.simonbinder.eu/docs/advanced-features/migrations/). ColumnBuilder check(Expression condition) => _isGenerated(); /// Declare a generated column. /// /// Generated columns are backed by an expression, declared with /// [generatedAs]: /// /// ```dart /// class Products extends Table { /// TextColumn get name => text()(); /// /// RealColumn get price => real()(); /// RealColumn get discount => real()(); /// RealColumn get tax => real()(); /// RealColumn get netPrice => real().generatedAs( /// price * (Constant(1) - discount) * (Constant(1) + tax))(); /// } /// ``` /// /// Generated columns can either be `VIRTUAL` (the default) or `STORED` /// (enabled with the [stored] parameter). Stored generated columns are /// computed on each update and are stored in the database. Virtual columns /// consume less space, but are re-computed on each read. /// /// Generated columns can't be updated or inserted (neither with the Dart API /// or though SQL queries), so they are not represented in companions. /// /// __Important__: When a generated column can be nullable, don't forget to /// call [BuildGeneralColumn.nullable] on it to reflect this in the generated /// code. /// Also, note that generated columns are part of your databases schema and /// cannot be updated easily. When changing the [generatedAs] expression for a /// column, you need to re-generate the table with a [TableMigration]. /// /// Note that generated columns are only available in sqlite3 version /// `3.31.0`. VirtualColumnBuilder generatedAs(Expression generatedAs, {bool stored = false}) => _isGenerated(); } /// Column builders available for both virtual and non-virtual columns. // ignore: library_private_types_in_public_api extension BuildGeneralColumn on _BaseColumnBuilder { /// By default, the field name will be used as the column name, e.g. /// `IntColumn get id = integer()` will have "id" as its associated name. /// Columns made up of multiple words are expected to be in camelCase and will /// be converted to snake_case (e.g. a getter called accountCreationDate will /// result in an SQL column called account_creation_date). /// To change this default behavior, use something like /// `IntColumn get id = integer((c) => c.named('user_id'))`. /// /// Note that using [named] __does not__ have an effect on the json key of an /// object. To change the json key, annotate this column getter with /// [JsonKey]. ColumnBuilder named(String name) => _isGenerated(); /// Marks this column as nullable. Nullable columns should not appear in a /// primary key. Columns are non-null by default. ColumnBuilder nullable() => _isGenerated(); /// Adds UNIQUE constraint to column. /// /// Unique constraints spanning multiple keys can be added to a table by /// overriding [Table.uniqueKeys]. ColumnBuilder unique() => _isGenerated(); /// Uses a custom [converter] to store custom Dart objects in a single column /// and automatically mapping them from and to sql. /// /// An example might look like this: /// ```dart /// // this is the custom object with we want to store in a column. It /// // can be as complex as you want it to be /// class MyCustomObject { /// final String data; /// MyCustomObject(this.data); /// } /// /// class CustomConverter extends TypeConverter { /// // this class is responsible for turning a custom object into a string. /// // this is easy here, but more complex objects could be serialized using /// // json or any other method of your choice. /// const CustomConverter(); /// @override /// MyCustomObject fromSql(String fromDb) { /// return fromDb == null ? null : MyCustomObject(fromDb); /// } /// /// @override /// String toSql(MyCustomObject value) { /// return value?.data; /// } /// } /// /// ``` /// /// In that case, you could have a table with this column /// ```dart /// TextColumn get custom => text().map(const CustomConverter())(); /// ``` /// The generated row class will then use a `MyFancyClass` instead of a /// `String`, which would usually be used for [Table.text] columns. /// /// The type [T] of the type converter may only be nullable if this column /// was declared [nullable] too. Otherwise, `drift_dev` will emit an error. ColumnBuilder map(TypeConverter converter) => _isGenerated(); /// Turns this column builder into a column. This method won't actually be /// called in your code. Instead, the generator will take a look at your /// source code to figure out your table structure. Column call() => _isGenerated(); } /// Tells the generator to build an [IntColumn]. See the docs at [ColumnBuilder] /// for details. extension BuildIntColumn on ColumnBuilder { /// Enables auto-increment for this column, which will also make this column /// the primary key of the table. /// /// For this reason, you can't use an [autoIncrement] column and also set a /// custom [Table.primaryKey] on the same table. ColumnBuilder autoIncrement() => _isGenerated(); } /// Tells the generator to build an [Int64Column]. See the docs at [ColumnBuilder] /// for details. extension BuildInt64Column on ColumnBuilder { /// Enables auto-increment for this column, which will also make this column /// the primary key of the table. /// /// For this reason, you can't use an [autoIncrement] column and also set a /// custom [Table.primaryKey] on the same table. ColumnBuilder autoIncrement() => _isGenerated(); } /// Tells the generator to build an [TextColumn]. See the docs at /// [ColumnBuilder] for details. extension BuildTextColumn on ColumnBuilder { /// Puts a constraint on the minimum and maximum length of text that can be /// stored in this column. /// /// Both [min] and [max] are inclusive. This constraint will be validated in /// Dart, it doesn't have an impact on the database schema. If [min] is not /// null and one tries to write a string which [String.length] is /// _strictly less_ than [min], an exception will be thrown. Similarly, you /// can't insert strings with a length _strictly greater_ than [max]. ColumnBuilder withLength({int? min, int? max}) => _isGenerated(); } /// Annotation to use on column getters inside of a [Table] to define the name /// of the column in the json used by [DataClass.toJson]. /// /// Example: /// ```dart /// class Users extends Table { /// IntColumn get id => integer().autoIncrement()(); /// @JsonKey('user_name') /// TextColumn get name => text().nullable()(); /// } /// ``` /// When calling [DataClass.toJson] on a `User` object, the output will be a map /// with the keys "id" and "user_name". The output would be "id" and "name" if /// the [JsonKey] annotation was omitted. class JsonKey { /// The key in the json map to use for this [Column]. See the documentation /// for [JsonKey] for details. final String key; /// An annotation to tell drift how the name of a column should appear in /// generated json. See the documentation for [JsonKey] for details. const JsonKey(this.key); } /// Annotation to use on reference columns inside of a [Table] to define the name /// that the manager will use when refering to this relation in the reverse. /// /// Example: /// ```dart /// class TodoEntries extends Table { /// IntColumn get id => integer().autoIncrement()(); /// TextColumn get body => text()(); /// @ReferenceName("categories") /// IntColumn get category => integer().nullable().references(Categories, #id)(); /// } /// class Categories extends Table { /// IntColumn get id => integer().autoIncrement()(); /// } /// /// The manager will now use the name `categories` /// categories.filter((f) => f.categories((f) => f.body("Todo"))) /// /// ``` /// When these aren't specified, the manager will use the referenced tables name followed by `Refs`. /// If a reference name clashes with other fields on the table, the generator will show a warning, /// and filters and orderings wont be generated class ReferenceName { /// The name that this reference will use when generating filters and ordering in the reverse direction /// for [ReferenceName] for details. final String name; /// Annotation to use on reference columns inside of a [Table] to define the name /// of the filters and orderings for the reverse relation. const ReferenceName(this.name); }