Generated code
For each schema the generator writes a *.gisila.g.dart file that imports
package:gisila_orm/gisila.dart. Don’t edit it; regenerate after every schema
change. The examples on this page use this schema:
Author: columns: name: { type: varchar, is_null: false } email: { type: varchar, is_null: false, is_unique: true, allow_blank: false }
Post: columns: title: { type: varchar, is_null: false } body: { type: text } published_at: { type: timestamp } author: type: Author is_null: false reverse_name: postsPer model you get four things:
| Generated | Example | Purpose |
|---|---|---|
| Model class | class Post with Preloadable |
An immutable row value. |
| Table class | class PostTable |
Typed ColumnRefs and the TableMeta. |
| Query accessor | Query<Post> posts() |
Shortcut for Query<Post>(PostTable.metadata). |
| Relations | Post.author, Author.posts |
Static Relation values for preload. |
Schema enums also become Dart enums.
Model class
Section titled “Model class”class Post with Preloadable { final int? id; final String title; final String? body; final DateTime? publishedAt; final int authorId;
Post({this.id, required this.title, this.body, this.publishedAt, required this.authorId});
factory Post.fromRow(Map<String, dynamic> row); Map<String, dynamic> toRow(); factory Post.fromJson(Map<String, dynamic> json); Map<String, dynamic> toJson({List<String> exclude = const [], List<String> only = const []}); Post copyWith({int? id, String? title, String? body, DateTime? publishedAt, int? authorId}); List<String> validate();
static final Relation<Post, Author> author = BelongsToRelation<Post, Author>(...); Author? get authorLoaded;}When to use: the model is what queries return. Build one yourself when you
need a value object (for example in tests); to write rows, pass a map to
insert instead.
Fields and constructor
Section titled “Fields and constructor”| Rule | Detail |
|---|---|
| Field names | camelCase of the column name; foreign keys use the physical column (author → authorId). |
| Nullability | Nullable when is_null: true. The primary key is always nullable (int? id), because the database assigns it on insert. |
required |
Parameters for is_null: false columns, except the primary key. |
| Many-to-many columns | Produce no field. |
const |
The constructor isn’t const, because Preloadable holds a mutable map. |
Members
Section titled “Members”| Member | Description |
|---|---|
fromRow(row) |
Builds a model from a column-name map, coercing driver values: DateTime.parse for date/timestamp strings, Vector.parse / Vector.fromList for vectors, Point.fromString and friends for geometry, <Enum>Gisila.parse for enums, toString() for decimals. Keys are physical (snake_case) column names. |
toRow() |
The reverse: physical column name → value ready to bind. Enums become .sqlValue, geometry becomes .toSqlLiteral(), JSON becomes encodeJsonColumn(value). Includes id, even when null. |
fromJson(json) |
Same as fromRow. |
toJson({exclude, only}) |
toRow() with DateTime values as ISO-8601 UTC strings and JSON columns as their decoded value, so jsonEncode(post.toJson()) works. only keeps just the listed keys; otherwise exclude removes keys. Keys are snake_case. |
copyWith(...) |
Copy with some fields replaced. Passing null keeps the current value, so you can’t clear a field with it. |
validate() |
Returns error strings such as Author.email must not be blank for allow_blank: false string columns. Empty when valid. Nothing calls it for you. |
preloaded<T>(name), isPreloaded(name), $preloaded |
From Preloadable. |
Relations and loaded accessors
Section titled “Relations and loaded accessors”| Schema | Static relation | Accessor after preload |
|---|---|---|
author: { type: Author } on Post |
Post.author (BelongsToRelation<Post, Author>) |
Author? get authorLoaded |
reverse_name: posts on that column |
Author.posts (HasManyRelation<Author, Post>) |
List<Post> get postsList |
Same, with is_unique: true on the FK |
Author.posts (HasOneRelation<Author, Post>) |
Post? get postsLoaded |
tags: { type: Tag, many_to_many: true, reverse_name: posts } |
Post.tags, Tag.posts (ManyToManyRelation) |
List<Tag> get tagsList, List<Post> get postsList |
Relations are static final, not const. Collection accessors return an empty
list when the relation wasn’t preloaded; single accessors return null.
Table class
Section titled “Table class”class PostTable { PostTable._();
static const ColumnRef<int?> id = ColumnRef<int?>(table: 'posts', column: 'id'); static const ColumnRef<String> title = ColumnRef<String>(table: 'posts', column: 'title'); static const ColumnRef<String?> body = ColumnRef<String?>(table: 'posts', column: 'body'); static const ColumnRef<DateTime?> publishedAt = ColumnRef<DateTime?>(table: 'posts', column: 'published_at'); static const ColumnRef<int> authorId = ColumnRef<int>(table: 'posts', column: 'author_id');
static const TableMeta<Post> metadata = TableMeta<Post>( tableName: 'posts', primaryKey: 'id', columnNames: ['id', 'title', 'body', 'published_at', 'author_id'], fromRow: Post.fromRow, );}When to use: reach for PostTable.<column> whenever you filter or sort
(where, orderBy),
and pass PostTable.metadata to Query<Post>(...).
Each ColumnRef<T> is typed with the model field’s Dart type, so
PostTable.title.eq(42) doesn’t compile. The type decides which extension
operators are available: like on ColumnRef<String> and ColumnRef<String?>,
array operators on ColumnRef<List<T>>, vector distances on
ColumnRef<Vector> and ColumnRef<Vector?>.
TableMeta<T>
Section titled “TableMeta<T>”Static metadata that a Query<T> carries: which
table to read and how to build a T from a row.
class TableMeta<T> { const TableMeta({ required String tableName, required List<String> columnNames, required RowMapper<T> fromRow, String primaryKey = 'id', });}When to use: generated for you. Write one by hand only to query a table that isn’t in your schema (a view, a legacy table) with the typed builder.
| Member | Type | Default | Description |
|---|---|---|---|
tableName |
String |
– | Unquoted SQL table name. |
columnNames |
List<String> |
– | Non-relation columns in schema order. Used as the default SELECT list and by the preloader. |
fromRow |
RowMapper<T> |
– | Builds a T from a column map. |
primaryKey |
String |
'id' |
Primary key column name. |
Example
import 'package:gisila_orm/gisila.dart';
class ActiveUser { ActiveUser(this.id, this.email); final int id; final String email;}
const activeUsers = TableMeta<ActiveUser>( tableName: 'active_users_view', columnNames: ['id', 'email'], fromRow: _activeUserFromRow,);
ActiveUser _activeUserFromRow(Map<String, dynamic> row) => ActiveUser(row['id'] as int, row['email'] as String);
Future<List<ActiveUser>> recent(DbContext db) => Query<ActiveUser>(activeUsers).limit(10).all(db);RowMapper<T> and Hydrator<T>
Section titled “RowMapper<T> and Hydrator<T>”typedef RowMapper<T> = T Function(Map<String, dynamic> row);
class Hydrator<T> { const Hydrator(RowMapper<T> mapper); List<T> hydrateAll(Iterable<ResultRow> rows); T hydrateOne(ResultRow row);}Hydrator turns driver ResultRows into models by calling
mapper(row.toColumnMap()). Every terminal operation uses it. Use it yourself
to hydrate the result of a hand-written query:
final result = await db.execute( 'SELECT * FROM posts WHERE to_tsvector(body) @@ plainto_tsquery(\$1)', parameters: ['dart'],);final posts = Hydrator<Post>(Post.fromRow).hydrateAll(result);Query accessor
Section titled “Query accessor”Query<Post> posts() => Query<Post>(PostTable.metadata);A top-level function named with the plural camelCase of the model (Author →
authors(), Category → categories()). Each call returns a new builder.
enum ReviewVerdict { pending, approved, rejected }
extension ReviewVerdictGisila on ReviewVerdict { String get sqlValue => name; static ReviewVerdict parse(String raw); // throws FormatException for unknown labels}Pass .sqlValue when you write an enum column through a map:
{'verdict': ReviewVerdict.approved.sqlValue}.
Output files
Section titled “Output files”| Schema files | Output |
|---|---|
One (lib/models/blog.gisila.yaml) |
blog.gisila.g.dart, blog.gisila.up.sql, blog.gisila.down.sql beside it |
| Two or more | lib/models/schema.gisila.g.dart (+ .up.sql, .down.sql). Without a lib/ directory, the common parent directory of the files. |
The .up.sql / .down.sql files are a full create / drop script for the
current schema, not an incremental migration. See
The codegen pipeline.
See also: Schema YAML format, Query builder, Relations and preloading.