simolus3 / drift

Drift is an easy to use, reactive, typesafe persistence library for Dart & Flutter.
https://drift.simonbinder.eu/
MIT License
2.65k stars 372 forks source link

Can't add custom type to table #2471

Closed xOldeVx closed 3 months ago

xOldeVx commented 1 year ago

I have a playlist object that contains a video object, i'm trying to add a video to the playlist table and i'm getting compile error, here's the error:

Here's the convertor code:

class VideoConverter extends TypeConverter<Video, String> {
  const VideoConverter();

  @override
  Video fromSql(String fromDb) {
    return Video.fromJson(json.decode(fromDb) as Map<String, dynamic>);
  }

  @override
  String toSql(Video value) {
    return json.encode(value.toJson());
  }
}

And the playlist table:

class PlaylistDB extends Table {
  IntColumn get id => integer().autoIncrement()();

  TextColumn get title => text().named('title')();

  TextColumn get videos => text().named('video').map(const VideoConverter())();
}

How can i insert specific video/list of videos to the playlist table?

Mike278 commented 1 year ago

The error is telling you that you need to wrap video in a Value. Have a look at the docs for updates, deletes, and inserts - the tl;dr is that Value is used to distinguish between "leave this column unchanged" when updating, "use the column's default value" when inserting, and "set this column to null" when inserting or updating.

simolus3 commented 1 year ago

Could you check that you're on the latest drift version as well? After copying your table and type converter, drift generates this for me:

  PlaylistDBCompanion.insert({
    this.id = const Value.absent(),
    required String title,
    required Video videos,
  })

So ideally you should just be able to pass the video directly. Since videos is not nullable in the table, it should be a required value in the insert constructor of the companion.

A Value wrapper is used when you have the choice between an explicit value and a default that would be computed in the database. You can see this with the id column: You could set it to a fixed id (id: Value(3)) or use the auto-incremented value (id: Value.absent(), the default).

Mike278 commented 1 year ago

videos is not nullable in the table

Oh right, I forgot Dart column definitions are non-null by default (which makes sense!)

xOldeVx commented 1 year ago

Thanks you, it's working! I'm saving a list of videos in the playlist object, it's working good, i can't find a way to remove specific video from the list, here's the code:

VideoModel video = VideoModel('videoId1', 'channelId', 'title');
await into(playlists).insert(PlaylistsCompanion.insert(
  title: 'video.title',
  videos: [video],
));

List<Playlist> result = await select(playlists).get();
await delete(playlists)..where((t) => t.videos);         <---- How remove specific video from List<Videos>
print(result);
class Playlists extends Table {
  IntColumn get id => integer().autoIncrement()();

  TextColumn get title => text().named('t')();

  TextColumn get videos => text().named('v').map(const VideoListConverter())();
}
class VideoListConverter extends TypeConverter<List<VideoModel>, String> {
  const VideoListConverter();

  @override
  List<VideoModel> fromSql(String fromDb) {
    return jsonDecode(fromDb).map((e) => VideoModel.fromJson(e)).toList().cast<VideoModel>();;
  }

  @override
  String toSql(List<VideoModel> value) {
    return json.encode(List<dynamic>.from(value.map((e) => e.toJson())));
  }
}
simolus3 commented 1 year ago

For those queries, I'd generally recommend a more typical relational design, where you have three tables (videos, playlist, video-in-playlist). That makes the select a lot more complicated, but it's much easier to remove a specific video from a playlist.

In your design, I think the easiest way would be to:

  1. Select videos from the playlist
  2. Remove the video in Dart
  3. Use an update to remove the video in the SQL.

This could look something like this:

await transaction(() async {
  final playlist = await (select(playlists)..where((row) => /* filter for the playlist where you want to delete a video */)).get();
  final videos = playlist.videos.toList();
  videos.remove(videoToRemove);
  await (update(playlists)..whereSamePrimaryKey(playlist)).write(PlaylistsCompanion(videos: Value(videos));
});
dickermoshe commented 3 months ago

No activity in over a year & seems to be resolved, closing