Skip to main content

sqlite_plus_generator 0.1.5 copy "sqlite_plus_generator: ^0.1.5" to clipboard
sqlite_plus_generator: ^0.1.5 copied to clipboard

Dart generator for sqlite_plus_orm mappings, row functions, JSON paths, FTS search, and R-tree bounds.

[sqlite_plus]

sqlite_plus_generator #

Write the class. This tool writes the columns, the revision, the JSON path, the full-text search, and the R-tree box. The analyzer stays on your machine. The application depends on sqlite_plus_orm, which runs on sqlite_plus.

dev_dependencies:
  sqlite_plus_generator: ^0.1.5
dart run sqlite_plus_generator

Run that from the package root. Put one entity in lib/book.dart. The annotation is the whole marker. The part directive is optional because the command adds it:

import 'package:sqlite_plus_orm/sqlite_plus_orm.dart';

@DbEntity()
class Book {
  Book({this.id, required this.title});
  final BigInt? id;
  final String title;
}

A second file, lib/note.dart, uses the same shape for Note. The command writes lib/book.orm.g.dart and lib/note.orm.g.dart, then lib/sqlite_plus_migrations.g.dart. That last library's generatedModel is every selected entity and foreign key. A class that only has an id field is not included.

The command prints each file as it reads it. A part whose source stamp still matches is left unchanged and is not generated again. A missing part is created. A changed class is updated. The run ends with a summary for the package, or for the one library passed to --input.

Found Book in lib/book.dart. Already generated. Leaving lib/book.orm.g.dart unchanged.
Found Note in lib/note.dart.
Generating lib/note.orm.g.dart.
Created lib/note.orm.g.dart.

Summary for the package
Unchanged: lib/book.orm.g.dart (Book)
Created: lib/note.orm.g.dart (Note)
Updated: lib/sqlite_plus_migrations.g.dart (Book, Note)
import 'package:your_app/book.dart' hide generatedModel;
import 'package:your_app/note.dart' hide generatedModel;
import 'package:your_app/sqlite_plus_migrations.g.dart';

final context = DbContext(connection, generatedModel);
await context.migrations.ensureCreated();
final books = context.set(BookMapping.instance);
final notes = context.set(NoteMapping.instance);
final plan = await context.migrations.plan();
await context.migrations.applySafe(plan);

One table does not need that context. sqlite_plus_orm opens it directly:

final books = await DbSet.openMemory(BookMapping.instance);
final notes = await DbSet.openDbFile(NoteMapping.instance, 'notes.db');

openMemory is one memory database for that table. openDbFile creates a missing file and an empty -journal, and a later open of the same path keeps the rows. On the Web openDbFile throws UnsupportedError. A second openDbFile on an open path throws StateError. Two types stay on one DbContext, and one saveChanges writes both. A second saveChanges while the first is running throws OrmStateException. Queries on that context wait, then run.

Each model library also defines generatedModel for its own tables. Hide those names and use the package model when one context owns every entity. A @DbReference still has to name a @DbEntity class in the same library. The package model copies those foreign keys.

ensureCreated creates the tables on an empty database. After that, plan and applySafe add a new nullable column. A rename or a destructive change still needs a reviewed plan.

One library stays available:

dart run sqlite_plus_generator --input lib/models.dart

--check compares the generated files with the files on disk and does not add a missing part directive. --class Book limits that one library. --output and --class need --input.

The classes #

import 'package:sqlite_plus_orm/sqlite_plus_orm.dart';

part 'models.orm.g.dart';

class Customer {
  Customer({this.id, required this.name, this.revision = 0});
  final BigInt? id;
  final String name;
  @DbVersion()
  final int revision;
}

class Purchase {
  Purchase({this.id, this.customerId, required this.total});
  BigInt? id;
  BigInt? customerId;
  double total;
}

class Note {
  Note({this.id, required this.document});
  final BigInt? id;
  @DbJson()
  final String document;
}

@DbEntity(module: 'fts5')
class Article {
  Article({this.rowid, required this.body});
  @DbKey(generated: true)
  final BigInt? rowid;
  final String body;
}

@DbEntity(module: 'rtree')
class Place {
  Place({
    required this.id,
    required this.minX,
    required this.maxX,
    required this.minY,
    required this.maxY,
  });
  final BigInt id;
  final double minX, maxX, minY, maxY;
}

customerId becomes PurchaseRelationships.customer. The part is models.orm.g.dart.

What you call #

final context = DbContext(connection, generatedModel);
await context.migrations.ensureCreated();

final purchases = context.set(PurchaseMapping.instance);
final ada = context.set(CustomerMapping.instance).add(Customer(name: 'Ada'));
final purchase = purchases.add(Purchase(total: 22.5));
purchases.add(Purchase(total: 4));
purchases.add(Purchase(total: 30));
purchase.reference(PurchaseRelationships.customer).set(ada);

final notes = context.set(NoteMapping.instance);
notes.add(Note(document: '{"title":"kept"}'));
final articles = context.set(ArticleMapping.instance);
articles.add(Article(body: 'alpha kept'));
final places = context.set(PlaceMapping.instance);
places.add(Place(id: BigInt.one, minX: 0, maxX: 1, minY: 0, maxY: 1));
await context.saveChanges();

final expensive = await purchases.query
    .selectWhere((row) => row.total > 20.0)
    .orderByFields((row) => [row.total.ascending])
    .select();
final large = await purchases.query
    .selectWhere((row) => row.total.abs() > 20)
    .toList();
final note = (await NoteQuery(notes.query)
        .filter((row) => row.path(r'$.title').equalTo('kept'))
        .toList())
    .single;
final article = (await ArticleQuery(articles.query).search('kept').toList()).single;
final inside = (await PlaceQuery(places.query)
        .filter((row) => row.bounds.intersects(
            const Rect(minX: 0, minY: 0, maxX: 1, maxY: 1)))
        .toList())
    .single;
final buyer = await purchase.reference(PurchaseRelationships.customer).load();

expensive is 22.5 then 30.0. large is those same two rows: abs is the engine, called on the column. note.document is {"title":"kept"}. article.body is alpha kept. inside is place 1. buyer is Ada.

A method on the class is a scalar too. It reads the fields. The query calls it with no arguments.

class Purchase {
  Purchase({this.id, this.customerId, required this.total});
  BigInt? id;
  BigInt? customerId;
  double total;

  @DbScalar()
  double doubled() => total * 2;
}

final matched = await purchases.query
    .selectWhere((row) => row.doubled() > 40)
    .toList();

matched is the 22.5 and 30 purchases: each total doubled is above 40. The part registers purchase_doubled.

After the first ensureCreated, a new nullable field is an up migration:

dart run sqlite_plus_generator --input lib/models.dart
final plan = await context.migrations.plan();
await context.migrations.applySafe(plan);

A renamed SQL column is not guessed. Keep @DbColumn(name: 'title') when only the Dart name changes. When the column itself changes, write the ALTER and approve that plan. The reverse statement is the down migration. The ORM page shows both.

The engine underneath #

1,000 rows on Linux, release build. The same SQL, and every checksum matched. In memory, a transaction of inserts was 263× and search was 107×. On a file, search was 142× and a point lookup was 104×.

[Memory, 1,000 rows.]

[File, 1,000 rows.]

What the part contains #

You write The part contains
A concrete public class EntityType, columns, filter, orderByFields
id or <ClassName>Id The primary key
@DbColumn(name: ...) That SQL name
@DbIgnore The field left out of the table
@DbKey(generated: true) A key assigned on insert
@DbVersion() The revision column
<Parent>Id or @DbReference reference and collection
@DbJson() on one String path
@DbScalar() on a method or getter row.twice(), registered as table_method
@DbEntity(module: 'fts5') USING fts5 and search
@DbEntity(module: 'rtree') USING rtree and bounds

The write is deterministic and formatted. Hand-written files and symbolic links stay in place. Fields are int, BigInt, double, bool, String, Uint8List, and DateTime, including nullable aliases. A generic, an inherited field, or an unsupported type is reported before any file is written.

The other packages #

sqlite_plus runs the SQL. One memory database:

import 'package:sqlite_plus/sqlite_plus.dart';

final db = await openInMemory();
await db.execute(
  'CREATE TABLE note (id INTEGER PRIMARY KEY, title TEXT)',
);

sqlite_plus_orm is what the application calls. saveChanges writes the tracked objects, and row.doubled() is the method above.

1
likes
0
points
1.5k
downloads

Publisher

verified publisherquds.cc

Weekly Downloads

Dart generator for sqlite_plus_orm mappings, row functions, JSON paths, FTS search, and R-tree bounds.

Repository (GitHub)
View/report issues

Topics

#sqlite #orm #codegen

License

unknown (license)

Dependencies

analyzer, args, crypto, path, sqlite_plus_orm

More

Packages that depend on sqlite_plus_generator