raindrop 0.1.1 copy "raindrop: ^0.1.1" to clipboard
raindrop: ^0.1.1 copied to clipboard

A type-safe SQL toolkit for Dart that requires no code generation.

example/lib/main.dart

import 'package:raindrop/raindrop.dart';
import 'package:raindrop_sqlite/raindrop_sqlite.dart';
import 'package:sqlite3/sqlite3.dart';

// A schema maps a row to your own type. Column handles are fields, so every
// query built from them is typed.
class User {
  const User({required this.name, this.id});

  final int? id;

  final String name;

  @override
  String toString() => 'User(id: $id, name: $name)';
}

class UserSchema extends Schema<User> {
  UserSchema(super.$)
      : id = $.integer('id', (u) => u.id).primaryKey(autoIncrement: true),
        name = $.text('name', (u) => u.name);

  final ColumnType<int?> id;

  final ColumnType<String> name;

  @override
  User fromRow(RowReader read) => User(id: read(id), name: read(name));
}

class Pet {
  const Pet({required this.name, required this.ownerId, this.id});

  final int? id;

  final int ownerId;

  final String name;

  @override
  String toString() => 'Pet(id: $id, ownerId: $ownerId, name: $name)';
}

class PetSchema extends Schema<Pet> {
  PetSchema(super.$)
      : id = $.integer('id', (p) => p.id).primaryKey(autoIncrement: true),
        ownerId = $
            .integer('owner_id', (p) => p.ownerId)
            .references(() => users.id, onDelete: ReferentialAction.cascade),
        name = $.text('name', (p) => p.name);

  final ColumnType<int?> id;

  final ColumnType<int> ownerId;

  final ColumnType<String> name;

  @override
  Pet fromRow(RowReader read) =>
      Pet(id: read(id), ownerId: read(ownerId), name: read(name));
}

// A driver's table function tags the table with its dialect.
final UserSchema users = sqliteTable('users', UserSchema.new);
final PetSchema pets = sqliteTable('pets', PetSchema.new);

// In an application these come from `raindrop_cli`, which diffs the schemas
// above into SQL. Here the first migration is written by hand.
const migrations = [
  Migration('0000_initial', '''
CREATE TABLE "users" (
  "id" INTEGER PRIMARY KEY AUTOINCREMENT,
  "name" TEXT NOT NULL
);
CREATE TABLE "pets" (
  "id" INTEGER PRIMARY KEY AUTOINCREMENT,
  "owner_id" INTEGER NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
  "name" TEXT NOT NULL
);'''),
];

Future<void> main() async {
  // Hand `Raindrop` your driver's delegate. Each driver's README shows its
  // own connection setup.
  final db = Raindrop(SQLiteDelegate(sqlite3.openInMemory()));

  // Apply the migrations that are still pending.
  await migrate(db, migrations);

  // Writes use the same typed handles. `returning()` yields the stored rows
  // as your own type.
  final [alex, sam] = await db.insert(into: users).values([
    const User(name: 'Alex'),
    const User(name: 'Sam'),
  ]).returning();
  print('Inserted $alex and $sam');

  await db.insert(into: pets).values([
    Pet(name: 'Rex', ownerId: alex.id!),
    Pet(name: 'Milo', ownerId: alex.id!),
    Pet(name: 'Smokey', ownerId: sam.id!),
  ]);

  // Select nothing and you get your row type back, a `List<User>`.
  final byName = await db.select().from(users).where(users.name.equals('Alex'));
  print('Users named Alex: $byName');

  // Select columns and you get a record of exactly those columns, here a
  // `List<(String, int)>`.
  final petsPerUser = await db
      .select(users.name, count(pets.id))
      .from(users)
      .join(pets, on: users.id.equals(pets.ownerId))
      .groupBy(users.id)
      .having(count(pets.id).greaterThan(1))
      .orderBy({users.name: Order.asc});
  print('Users with more than one pet: $petsPerUser');

  // Subqueries are typed the same way.
  final owners = await db.select(users.name).from(users).where(
        exists(db.select().from(pets).where(users.id.equals(pets.ownerId))),
      );
  print('Users that own a pet: $owners');

  // A transaction runs its body against a transactional executor. A throw
  // rolls it back.
  await db.transaction((tx) async {
    final [robin] = await tx.insert(into: users).values([
      const User(name: 'Robin'),
    ]).returning();
    await tx.insert(into: pets).values([
      Pet(name: 'Nibbles', ownerId: robin.id!),
    ]);
  });

  await db
      .update(users)
      .set(users.name.to('Alexandra'))
      .where(users.id.equals(alex.id));

  // The delete cascades to Rex and Milo through the foreign key.
  await db.delete(from: users).where(users.name.equals('Alexandra'));
  print('Remaining pets: ${await db.select().from(pets)}');
}
2
likes
160
points
163
downloads

Documentation

API reference

Publisher

verified publisherwolfenrain.dev

Weekly Downloads

A type-safe SQL toolkit for Dart that requires no code generation.

Repository (GitHub)
View/report issues
Contributing

Topics

#sql #database #orm #migrations

License

MIT (license)

Dependencies

crypto, meta, path

More

Packages that depend on raindrop