ConverterException: Parameter likes is not a List
mhmzdev opened this issue · 4 comments
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;
}
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
@mhmzdev This is already merged. When updating postgres it should work without any changes to stormberry.
Awesome!! @schultek
Thank you so much for figuring this out.
Lemme, try updating postgres and see if it works
Worked like a charm! ✅
Closing the issue.