Skip to content

Commit c24f34b

Browse files
authored
Documentation for @DefaultValue (#305)
1 parent c77feba commit c24f34b

File tree

9 files changed

+945
-21
lines changed

9 files changed

+945
-21
lines changed

typed_sql/doc/01_schema.md

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ We must then define a _schema class_. This is an `abstract final class`
1515
extending [Schema], specifying what tables the database has.
1616
If we were working on a bookstore we might define a schema as follows:
1717

18-
```dart schema_test.dart#schema
18+
```dart schema_references_test.dart#schema
1919
abstract final class Bookstore extends Schema {
2020
Table<Author> get authors;
2121
Table<Book> get books;
@@ -37,7 +37,7 @@ For each table in our database we must define a _row class_. This is an
3737
the database table has. Continuing with the bookstore example we can define a
3838
_row class_ for the `authors` table as follows:
3939

40-
```dart schema_test.dart#author-model
40+
```dart schema_references_test.dart#author-model
4141
@PrimaryKey(['authorId'])
4242
abstract final class Author extends Row {
4343
@AutoIncrement()
@@ -95,7 +95,7 @@ _row class_ for the `books` table in the `Bookstore` schema. If we want the
9595
`books` table to have a _foreign key_ referencing the `authors` table we can
9696
define the `Book` _row class_ as follows:
9797

98-
```dart schema_test.dart#book-model
98+
```dart schema_references_test.dart#book-model
9999
@PrimaryKey(['bookId'])
100100
abstract final class Book extends Row {
101101
@AutoIncrement()
@@ -123,9 +123,6 @@ abstract final class Book extends Row {
123123
}
124124
```
125125

126-
The `@DefautValue(0)` annotation gives the `stock` field a default value of `0`.
127-
This also makes the `stock` field optional when inserting rows.
128-
129126
> [!NOTE]
130127
> The `name` and `as` properties in the `@References` annotation are optional.
131128
> These gives rise to convinient subquery properties we can use when writing
@@ -145,6 +142,53 @@ CREATE TABLE books (
145142
Notice that because the `title` field is nullable, it does not have a `NOT NULL`
146143
constraint in the database.
147144

145+
## Adding a default value
146+
In our bookstore example above, the _row class_ `Book` has a `stock` field
147+
annotated with `@DefaultValue(0)`. This gives the `stock` field a default value
148+
of `0`, and thus, causes the `stock` field to be optional when inserting rows.
149+
150+
```dart schema_default_value_test.dart#book-model
151+
@PrimaryKey(['bookId'])
152+
abstract final class Book extends Row {
153+
@AutoIncrement()
154+
int get bookId;
155+
156+
String? get title;
157+
158+
@References(table: 'authors', field: 'authorId', name: 'author', as: 'books')
159+
int get authorId;
160+
161+
@DefaultValue(0) // Gives the `stock` field to have a default value!
162+
int get stock;
163+
}
164+
```
165+
166+
The equivalent SQL depends on the database, but it looks something like:
167+
```sql
168+
CREATE TABLE books (
169+
bookId BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
170+
title TEXT,
171+
authorId BIGINT NOT NULL,
172+
stock BIGINT NOT NULL DEFAULT 0,
173+
FOREIGN KEY (authorId) REFERENCES authors (authorId)
174+
);
175+
```
176+
177+
Notice the `DEFAULT 0` clause in the declaration of the `stock` field.
178+
179+
It is possible to define default values for `String`, `bool`, `int` and `double`
180+
fields, using the `@DefaultValue(...)` annotation. However, since [DateTime]
181+
does not have a _const_ constructor one of the following annoations must be
182+
used to create a [DateTime] field with a _default value_:
183+
* `@DefaultValue.dateTime(year, [month, day, ...])`,
184+
* `@DefaultValue.epoch`, or,
185+
* `@DefaultValue.now`.
186+
187+
With [DefaultValue.now] being a special constructor that uses
188+
`CURRENT_TIMESTAMP` in the database to ensure the field
189+
has a default value of `DateTime.now().toUtc()` when row is inserted.
190+
191+
See [DefaultValue] for further documentation on _default values_.
148192

149193
## Generating code
150194
Whenever the definition of the database schema is changed, it's important to
@@ -177,7 +221,7 @@ your database, as well as how you are connecting.
177221

178222
Once you have `Database<Bookstore>` instance you can create empty tables for
179223
your schema as follows:
180-
```dart schema_test.dart#create-tables
224+
```dart schema_references_test.dart#create-tables
181225
// Create tables
182226
await db.createTables();
183227
```
@@ -186,7 +230,7 @@ Creating empty tables from scratch is mostly useful for testing, it's rarely
186230
needed in production. Instead you can use the generated `create<Schema>Tables`,
187231
which outputs the [DDL] for creating the tables.
188232

189-
```dart schema_test.dart#get-ddl
233+
```dart schema_references_test.dart#get-ddl
190234
// Get the database schema
191235
final ddl = createBookstoreTables(SqlDialect.postgres());
192236
```
@@ -204,7 +248,7 @@ database migrations. See [Migrations documentation][Migrations].
204248
With a `Database<Bookstore>` and tables created through migrations or
205249
`db.createTables()` you can insert data into the database as follows:
206250

207-
```dart schema_test.dart#insert-data
251+
```dart schema_references_test.dart#insert-data
208252
// Insert a row into the "authors" table
209253
final author = await db.authors
210254
.insert(
@@ -226,7 +270,7 @@ Now we can also write queries against the database. The following demonstrates
226270
how to write a query that filters on the book title and only returns `title`
227271
and `author.name`.
228272

229-
```dart schema_test.dart#query-data
273+
```dart schema_references_test.dart#query-data
230274
// Query for books where the title contains 'eggs'
231275
// select the title and author name
232276
final titleAndAuthor = await db.books

typed_sql/doc/03_writing_queries.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ Recall that `Author.name` is annotated with `@Unique()`. This means that the
605605
_name_ field in the database will have `UNIQUE` constraint. But also that
606606
`Query<(Expr<Author>,)>` will have a convenient `.byName` method.
607607

608-
```dart schema_test.dart#author-model
608+
```dart schema_references_test.dart#author-model
609609
@PrimaryKey(['authorId'])
610610
abstract final class Author extends Row {
611611
@AutoIncrement()

typed_sql/doc/05_foreign_keys.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ these point to the _table_ and _field_ that is being referenced.
5959
The `name` and `as` properties are optional, if present they will be used to
6060
generate convenient subquery properties when building queries.
6161

62-
```dart schema_test.dart#book-model
62+
```dart schema_references_test.dart#book-model
6363
@PrimaryKey(['bookId'])
6464
abstract final class Book extends Row {
6565
@AutoIncrement()

typed_sql/lib/src/typed_sql.annotations.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ final class PrimaryKey {
2323
const PrimaryKey(this.fields);
2424
}
2525

26-
/// Annotation for a field that has a default value.
26+
/// Annotation that assigns a _default value_ to a field.
27+
///
28+
/// Using this annotation on a non-nullable field, also causes the field
29+
/// to be _optional_ when inserting rows.
2730
///
2831
/// {@category schema}
2932
final class DefaultValue {
@@ -39,7 +42,25 @@ final class DefaultValue {
3942
const DefaultValue._(String kind, Object value)
4043
: _value = (kind: kind, value: value);
4144

45+
/// Annotate a field with a constant default value.
46+
///
47+
/// The [value] must be one of the following types:
48+
/// * [String],
49+
/// * [bool],
50+
/// * [int], or,
51+
/// * [double].
52+
///
53+
/// Consequently, this constructor can only be used to annotate a
54+
/// _default value_ to a field of a matching type.
4255
const DefaultValue(Object value) : _value = (kind: 'raw', value: value);
56+
57+
/// Annotate a [DateTime] field with a fixed default value.
58+
///
59+
/// This constructor behaves similar to [DateTime.utc], as arguments will all
60+
/// be interpreted as UTC.
61+
///
62+
/// To use `CURRENT_TIMESTAMP` as default value for a field, see
63+
/// [DefaultValue.now].
4364
const DefaultValue.dateTime(
4465
int year, [
4566
int month = 1,
@@ -63,7 +84,16 @@ final class DefaultValue {
6384
),
6485
);
6586

87+
/// Annotate a [DateTime] field with a default value of
88+
/// `1970-01-01T00:00:00Z`.
6689
static const DefaultValue epoch = DefaultValue._('datetime', 'epoch');
90+
91+
/// Annotate a [DateTime] field with a default value of `CURRENT_TIMESTAMP`.
92+
///
93+
/// This will cause the field to have a default value of the current timestamp
94+
/// in UTC, when a row is inserted.
95+
///
96+
/// See [Expr.currentTimestamp] for further details.
6797
static const DefaultValue now = DefaultValue._('datetime', 'now');
6898
}
6999

typed_sql/test/typed_sql/example/bookstore/bookstore_test.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import '../../testrunner.dart';
2121

2222
part 'bookstore_test.g.dart';
2323

24-
// Remark: please keep this schema in sync with ../schema/schema_test.dart
24+
// Remark: please keep this schema in sync with ../schema/**_test.dart
2525
// Documentation will assume that both of them uses the same schema,
26-
// we just have different clips of the regions, and schema_test.dart
27-
// has more comments.
26+
// we just have different clips of the regions and comments in:
27+
// - schema_default_value_test.dart
28+
// - schema_references_test.dart
2829

2930
// #region bookstore-schema
3031
abstract final class Bookstore extends Schema {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'package:typed_sql/typed_sql.dart';
16+
17+
import '../../../testrunner.dart';
18+
19+
part 'schema_default_value_test.g.dart';
20+
21+
// Remark: please keep this schema in sync with ../bookstore/bookstore_test.dart
22+
// Documentation will assume that both of them uses the same schema,
23+
// we just have different clips of the regions and comments in:
24+
// - schema_default_value_test.dart
25+
// - schema_references_test.dart
26+
27+
abstract final class Bookstore extends Schema {
28+
Table<Author> get authors;
29+
Table<Book> get books;
30+
}
31+
32+
@PrimaryKey(['authorId'])
33+
abstract final class Author extends Row {
34+
@AutoIncrement()
35+
int get authorId;
36+
37+
@Unique()
38+
@SqlOverride(dialect: 'mysql', columnType: 'VARCHAR(255)') // #hide
39+
String get name;
40+
}
41+
42+
// #region book-model
43+
@PrimaryKey(['bookId'])
44+
abstract final class Book extends Row {
45+
@AutoIncrement()
46+
int get bookId;
47+
48+
String? get title;
49+
50+
@References(table: 'authors', field: 'authorId', name: 'author', as: 'books')
51+
int get authorId;
52+
53+
@DefaultValue(0) // Gives the `stock` field to have a default value!
54+
int get stock;
55+
}
56+
// #endregion
57+
58+
void main() {
59+
final r = TestRunner<Bookstore>();
60+
61+
r.addTest('use the database', (db) async {
62+
// Create tables
63+
await db.createTables();
64+
65+
// Get the database schema
66+
final ddl = createBookstoreTables(SqlDialect.postgres());
67+
check(ddl).isNotEmpty();
68+
});
69+
70+
r.run();
71+
}

0 commit comments

Comments
 (0)