Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConverterException: Parameter likes is not a List #50

Closed
mhmzdev opened this issue Feb 25, 2023 · 4 comments
Closed

ConverterException: Parameter likes is not a List #50

mhmzdev opened this issue Feb 25, 2023 · 4 comments

Comments

@mhmzdev
Copy link

mhmzdev commented Feb 25, 2023

I've added just a List<int> likes into the Post model. Then added some dummy data in postgres SQL.
But it seems that stormberry isn't parsing the list correctly.

The rest of the models are working fine, only with List in them are throwing this exception.
I'm using 0.11.0 latest version

ConverterException: Parameter likes is not a List
package:stormberry/src/internals/text_encoder.dart 137:7   TypedMap.getList
package:stormberry/src/internals/text_encoder.dart 147:12  TypedMap.getListOpt
package:db/src/models/post/post.schema.dart 143:18         PostQueryable.decode
package:stormberry/src/internals/view_query.dart 48:46     ViewQuery.apply.<fn>
dart:_internal                                             ListIterable.toList
package:stormberry/src/internals/view_query.dart 48:83     ViewQuery.apply

Model class and its schema file code is below:

post.dart

@Model()
abstract class Post {
  @PrimaryKey()
  @AutoIncrement()
  int get id;

  int get uid;
  String get caption;
  bool? get hasImage;
  String? get imageUrl;
  bool? get hasVideo;
  String? get videoUrl;
  List<int> get likes;
  List<Comment> get comments;

  DateTime get createdAt;
}

post.schema.dart

part of 'post.dart';

extension PostRepositories on Database {
  PostRepository get posts => PostRepository._(this);
}

abstract class PostRepository
    implements
        ModelRepository,
        KeyedModelRepositoryInsert<PostInsertRequest>,
        ModelRepositoryUpdate<PostUpdateRequest>,
        ModelRepositoryDelete<int> {
  factory PostRepository._(Database db) = _PostRepository;

  Future<Post?> queryPost(int id);
  Future<List<Post>> queryPosts([QueryParams? params]);
}

class _PostRepository extends BaseRepository
    with
        KeyedRepositoryInsertMixin<PostInsertRequest>,
        RepositoryUpdateMixin<PostUpdateRequest>,
        RepositoryDeleteMixin<int>
    implements PostRepository {
  _PostRepository(super.db) : super(tableName: 'posts', keyName: 'id');

  @override
  Future<Post?> queryPost(int id) {
    return queryOne(id, PostQueryable());
  }

  @override
  Future<List<Post>> queryPosts([QueryParams? params]) {
    return queryMany(PostQueryable(), params);
  }

  @override
  Future<List<int>> insert(List<PostInsertRequest> requests) async {
    if (requests.isEmpty) return [];
    var values = QueryValues();
    var rows = await db.query(
      'INSERT INTO "posts" ( "uid", "caption", "has_image", "image_url", "has_video", "video_url", "likes", "created_at" )\n'
      'VALUES ${requests.map((r) => '( ${values.add(r.uid)}:int8, ${values.add(r.caption)}:text, ${values.add(r.hasImage)}:bool, ${values.add(r.imageUrl)}:text, ${values.add(r.hasVideo)}:bool, ${values.add(r.videoUrl)}:text, ${values.add(r.likes)}:_int8, ${values.add(r.createdAt)}:timestamp )').join(', ')}\n'
      'RETURNING "id"',
      values.values,
    );
    var result = rows.map<int>((r) => TextEncoder.i.decode(r.toColumnMap()['id'])).toList();

    return result;
  }

  @override
  Future<void> update(List<PostUpdateRequest> requests) async {
    if (requests.isEmpty) return;
    var values = QueryValues();
    await db.query(
      'UPDATE "posts"\n'
      'SET "uid" = COALESCE(UPDATED."uid", "posts"."uid"), "caption" = COALESCE(UPDATED."caption", "posts"."caption"), "has_image" = COALESCE(UPDATED."has_image", "posts"."has_image"), "image_url" = COALESCE(UPDATED."image_url", "posts"."image_url"), "has_video" = COALESCE(UPDATED."has_video", "posts"."has_video"), "video_url" = COALESCE(UPDATED."video_url", "posts"."video_url"), "likes" = COALESCE(UPDATED."likes", "posts"."likes"), "created_at" = COALESCE(UPDATED."created_at", "posts"."created_at")\n'
      'FROM ( VALUES ${requests.map((r) => '( ${values.add(r.id)}:int8, ${values.add(r.uid)}:int8, ${values.add(r.caption)}:text, ${values.add(r.hasImage)}:bool, ${values.add(r.imageUrl)}:text, ${values.add(r.hasVideo)}:bool, ${values.add(r.videoUrl)}:text, ${values.add(r.likes)}:_int8, ${values.add(r.createdAt)}:timestamp )').join(', ')} )\n'
      'AS UPDATED("id", "uid", "caption", "has_image", "image_url", "has_video", "video_url", "likes", "created_at")\n'
      'WHERE "posts"."id" = UPDATED."id"',
      values.values,
    );
  }
}

class PostInsertRequest {
  PostInsertRequest({
    required this.uid,
    required this.caption,
    this.hasImage,
    this.imageUrl,
    this.hasVideo,
    this.videoUrl,
    required this.likes,
    required this.createdAt,
  });

  int uid;
  String caption;
  bool? hasImage;
  String? imageUrl;
  bool? hasVideo;
  String? videoUrl;
  List<int> likes;
  DateTime createdAt;
}

class PostUpdateRequest {
  PostUpdateRequest({
    required this.id,
    this.uid,
    this.caption,
    this.hasImage,
    this.imageUrl,
    this.hasVideo,
    this.videoUrl,
    this.likes,
    this.createdAt,
  });

  int id;
  int? uid;
  String? caption;
  bool? hasImage;
  String? imageUrl;
  bool? hasVideo;
  String? videoUrl;
  List<int>? likes;
  DateTime? createdAt;
}

class PostQueryable extends KeyedViewQueryable<Post, int> {
  @override
  String get keyName => 'id';

  @override
  String encodeKey(int key) => TextEncoder.i.encode(key);

  @override
  String get query => 'SELECT "posts".*, "comments"."data" as "comments"'
      'FROM "posts"'
      'LEFT JOIN ('
      '  SELECT "comments"."post_id",'
      '    to_jsonb(array_agg("comments".*)) as data'
      '  FROM (${CommentQueryable().query}) "comments"'
      '  GROUP BY "comments"."post_id"'
      ') "comments"'
      'ON "posts"."id" = "comments"."post_id"';

  @override
  String get tableAlias => 'posts';

  @override
  Post decode(TypedMap map) => PostView(
      id: map.get('id'),
      uid: map.get('uid'),
      caption: map.get('caption'),
      hasImage: map.getOpt('has_image'),
      imageUrl: map.getOpt('image_url'),
      hasVideo: map.getOpt('has_video'),
      videoUrl: map.getOpt('video_url'),
      likes: map.getListOpt('likes') ?? const [],
      comments: map.getListOpt('comments', CommentQueryable().decoder) ?? const [],
      createdAt: map.get('created_at'));
}

class PostView with Post {
  PostView({
    required this.id,
    required this.uid,
    required this.caption,
    this.hasImage,
    this.imageUrl,
    this.hasVideo,
    this.videoUrl,
    required this.likes,
    required this.comments,
    required this.createdAt,
  });

  @override
  final int id;
  @override
  final int uid;
  @override
  final String caption;
  @override
  final bool? hasImage;
  @override
  final String? imageUrl;
  @override
  final bool? hasVideo;
  @override
  final String? videoUrl;
  @override
  final List<int> likes;
  @override
  final List<Comment> comments;
  @override
  final DateTime createdAt;
}
@schultek
Copy link
Owner

I found the issue. Its with int4 vs int8.
Stormberry uses int8 by default for all integer types, including lists of ints -> _int8. However the postgres package currently only supports _int4 (list of int4) and therefore has a type mismatch both when inserting and querying data.

For querying you can work around this by changing the likes database column manually to _int4.
For inserting/updating you would have to modify the post.schema.dart file and replace all :_int8 with :_int4.

I will try to open a pr on the postgres package to add support, but that might take some time until released

@schultek
Copy link
Owner

@mhmzdev This is already merged. When updating postgres it should work without any changes to stormberry.

@mhmzdev
Copy link
Author

mhmzdev commented Feb 27, 2023

Awesome!! @schultek
Thank you so much for figuring this out.

Lemme, try updating postgres and see if it works

@mhmzdev
Copy link
Author

mhmzdev commented Feb 27, 2023

Worked like a charm! ✅
Closing the issue.

@mhmzdev mhmzdev closed this as completed Feb 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants