diff --git a/docs/getting-started/setup.md b/docs/getting-started/setup.md index 488b51ec..4c6634cd 100644 --- a/docs/getting-started/setup.md +++ b/docs/getting-started/setup.md @@ -5,15 +5,15 @@ Install Prisma Dart Client, you need to create a `prisma/schema.prisma` file in ::: code-group ```bash [Bun.js] -bun prisma init --generator-provider="dart run orm" +bun prisma init ``` ```bash [NPM] -npx prisma init --generator-provider="dart run orm" +npx prisma init ``` ```bash [pnpm] -pnpx prisma init --generator-provider="dart run orm" +pnpx prisma init ``` ::: diff --git a/examples/with_sqlite/bin/with_sqlite.dart b/examples/with_sqlite/bin/with_sqlite.dart index 45f28c2f..51e1801d 100644 --- a/examples/with_sqlite/bin/with_sqlite.dart +++ b/examples/with_sqlite/bin/with_sqlite.dart @@ -2,17 +2,44 @@ import 'package:orm/orm.dart'; import 'package:with_sqlite/with_sqlite.dart'; final prisma = PrismaClient(log: { - (LogLevel.info, LogEmit.stdout), + (LogLevel.query, LogEmit.stdout), }); Future main() async { try { - final games = await prisma.$transaction((prisma) async { + // With transaction run query + final gamesWithTransaction = await prisma.$transaction((prisma) async { + final result = await prisma.game.aggregate( + select: AggregateGameSelect( + $count: PrismaUnion.$2( + AggregateGameCountArgs( + select: GameCountAggregateOutputTypeSelect(id: true), + ), + ), + ), + ); + if (result.$count!.id! <= 3) { + await prisma.game.create( + data: PrismaUnion.$2( + GameUncheckedCreateInput(name: 'Game ${result.$count?.id}'), + ), + ); + } + return await prisma.game.findMany(); }); + print(gamesWithTransaction.map((e) => e.toJson())); - // final games = await prisma.game.findMany(); + // Find many + final games = await prisma.game.findMany(); print(games.map((e) => e.toJson())); + + // SQL query all rows. + final result = await prisma.$raw.query('SELECT * FROM games'); + print(result); + + // Delete a `game_id` eq "1" row, DB not found "1", returns `0` + print(await prisma.$raw.execute(r'delete from games where game_id = "1"')); } finally { await prisma.$disconnect(); } diff --git a/packages/orm/CHANGELOG.md b/packages/orm/CHANGELOG.md index 006d10e8..68d54aa4 100644 --- a/packages/orm/CHANGELOG.md +++ b/packages/orm/CHANGELOG.md @@ -1,18 +1,35 @@ -## v5.0.7 +## v5.1.0 -To install Prisma ORM for Dart v5.0.7 run this command +To install Prisma ORM for Dart v5.1.0 run this command ```bash -dart pub add orm:^5.0.7 +dart pub add orm:^5.1.0 ``` Or update your `pubspec.yaml` file: ```yaml dependencies: - orm: ^5.0.7 + orm: ^5.1.0 ``` +### What's Changed + +- **upstream**: Starting from Prisma v5.17.0, the new RAW raw SQL protocol has been enabled. +- **$raw**: Now `$raw.query` returns the correct data type front, instead of `dynamic`. +- **$raw**: `$raw.execute` now returns the number of rows affected as an `int`. +- **docs**: Removed param from docs that are no longer supported by the Prisma CLI + +#### QueryRaw performance improvements + +We’ve changed the response format of queryRaw to decrease its average size which reduces serialization CPU overhead. + +When querying large data sets, we expect you to see improved memory usage and up to 2x performance improvements. + +## ‼️Important Tips + +Starting from version v5.1.0, only Prisma CLI v5.17.0 or higher is supported! + ## v5.0.6 To install Prisma ORM for Dart v5.0.6 run this command @@ -28,7 +45,7 @@ dependencies: orm: ^5.0.6 ``` -## What's Changed +### What's Changed - **deps**: Upgrade the Dart SDK min version to `^3.4.0` - **deps**: Upgrade `path` package version to `^1.9.0` diff --git a/packages/orm/lib/src/runtime/raw/_deserialize_raw_results.dart b/packages/orm/lib/src/runtime/raw/_deserialize_raw_results.dart new file mode 100644 index 00000000..c45a07e6 --- /dev/null +++ b/packages/orm/lib/src/runtime/raw/_deserialize_raw_results.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; + +import '../decimal.dart'; + +extension type _TypedRawResult._(Map _) { + Iterable get columns => (_['columns'] as Iterable).cast(); + Iterable get types => (_['types'] as Iterable).cast(); + Iterable get rows => (_['rows'] as Iterable).cast(); +} + +List> deserializeRawResult(Map result) { + final _TypedRawResult(:columns, :types, :rows) = _TypedRawResult._(result); + final deserialized = >[]; + + for (final row in rows) { + final mapped = {}; + for (final (index, value) in row.indexed) { + mapped[columns.elementAt(index)] = _decode(types.elementAt(index), value); + } + + deserialized.add(mapped); + } + + return deserialized; +} + +Object? _decode(String type, Object? value) { + return switch (type) { + 'bigint' => BigInt.parse(value.toString()), + 'bytes' => base64.decode(value.toString()), + 'decimal' => Decimal.parse(value.toString()), + 'datetime' || 'date' => DateTime.parse(value.toString()), + 'time' => DateTime.parse('1970-01-01T${value}Z'), + 'bigint-array' => _decodeIterable('bigint', value as Iterable), + 'bytes-array' => _decodeIterable('bytes', value as Iterable), + 'decimal-array' => _decodeIterable('decimal', value as Iterable), + 'datetime-array' => _decodeIterable('datetime', value as Iterable), + 'date-array' => _decodeIterable('date', value as Iterable), + 'time-array' => _decodeIterable('time', value as Iterable), + _ => value, + }; +} + +List _decodeIterable(String type, Iterable values) { + return values.map((value) => _decode(type, value)).toList(growable: false); +} diff --git a/packages/orm/lib/src/runtime/raw/_internal/raw_parameter_codec.dart b/packages/orm/lib/src/runtime/raw/_internal/raw_parameter_codec.dart deleted file mode 100644 index 763a5d18..00000000 --- a/packages/orm/lib/src/runtime/raw/_internal/raw_parameter_codec.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import '../../json_convertible.dart'; -import '../../decimal.dart'; -import '../../prisma_null.dart'; - -/// A codec for encoding and decoding Prisma raw parameters. -/// -/// This codec is used to encode and decode raw parameters in the Prisma Dart runtime. -/// It is used to convert Dart objects into their raw representation that can be sent -/// over the network or stored in a database. -const Codec rawParameter = _RawParameterCodec(); - -class _RawParameterCodec extends Codec { - const _RawParameterCodec(); - - @override - Converter get decoder => const _RawParameterDecoder(); - - @override - Converter get encoder => const _RawParameterEncoder(); -} - -class _RawParameterDecoder extends Converter { - const _RawParameterDecoder(); - - @override - convert(input) { - return switch (input) { - { - 'prisma__type': final String type, - 'prisma__value': final Object? value, - } => - deserialize(type, value.toString()), - Iterable value => value.map(convert).toList(), - Map value => value.map((key, value) => MapEntry(key, convert(value))), - _ => input, - }; - } - - deserialize(String type, Object? value) { - return switch (type) { - 'date' || 'datatime' => DateTime.parse(value.toString()), - 'time' => DateTime.parse('1970-01-01T${value}Z'), - 'bigint' => BigInt.parse(value.toString()), - 'decimal' => Decimal.parse(value.toString()), - 'bytes' => base64.decode(value.toString()), - _ => value, - }; - } -} - -class _RawParameterEncoder extends Converter { - const _RawParameterEncoder(); - - @override - convert(input) { - return switch (input) { - String() || int() || double() || bool() || null => input, - PrismaNull() => null, - BigInt value => typed('bigint', value.toString()), - DateTime value => typed('date', value.toUtc().toIso8601String()), - Decimal value => typed('decimal', value.toString()), - Uint8List bytes => typed('bytes', base64.encode(bytes)), - JsonConvertible value => typed('json', value.toJson()), - - // If the input is a iterable, convert each item in the iterable. - Iterable value => value.map(convert).toList(), - - // If the input is a map, convert each value in the map. - Map value => value.map((key, value) => MapEntry(key, convert(value))), - - // Otherwise, encode the input as JSON. - _ => typed('json', json.encode(input)), - }; - } - - Map typed(String type, String value) { - return { - 'prisma__type': type, - 'prisma__value': value, - }; - } -} diff --git a/packages/orm/lib/src/runtime/raw/_serialize_raw_params.dart b/packages/orm/lib/src/runtime/raw/_serialize_raw_params.dart new file mode 100644 index 00000000..5086d0ba --- /dev/null +++ b/packages/orm/lib/src/runtime/raw/_serialize_raw_params.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import '../decimal.dart'; +import '../json_convertible.dart'; +import '../prisma_null.dart'; + +/// Serialize RAW params. +String serializeRawParams(Iterable params) { + return json.encode( + params.map(_encode).toList(growable: false), + ); +} + +Object? _encode(Object? value) { + return switch (value) { + PrismaNull() => null, + BigInt value => _createTypedValue('bigint', value.toString()), + DateTime value => + _createTypedValue('date', value.toUtc().toIso8601String()), + Decimal value => _createTypedValue('decimal', value.toString()), + Uint8List bytes => _createTypedValue('bytes', base64.encode(bytes)), + TypedData typedData => _encode(typedData.buffer), + ByteBuffer byteBuffer => _encode(byteBuffer.asUint8List()), + JsonConvertible value => _createTypedValue('json', value.toJson()), + Iterable iterable => iterable.map(_encode).toList(growable: false), + Map value => + value.map((key, value) => MapEntry(key.toString(), _encode(value))), + _ => value, + }; +} + +Map _createTypedValue(String type, Object value) { + return { + 'prisma__type': type, + 'prisma__value': value, + }; +} diff --git a/packages/orm/lib/src/runtime/raw/raw_client.dart b/packages/orm/lib/src/runtime/raw/raw_client.dart index 884372c0..e6b5cc72 100644 --- a/packages/orm/lib/src/runtime/raw/raw_client.dart +++ b/packages/orm/lib/src/runtime/raw/raw_client.dart @@ -1,36 +1,45 @@ -import 'dart:convert'; - import '../../base_prisma_client.dart'; import '../json_protocol/protocol.dart'; import '../json_protocol/serialize.dart'; -import '_internal/raw_parameter_codec.dart'; +import '_serialize_raw_params.dart'; +import '_deserialize_raw_results.dart'; class RawClient> { final Client _client; const RawClient(Client client) : _client = client; - Future query(String sql, [Iterable? parameters]) => _inner( - action: JsonQueryAction.queryRaw, - sql: sql, - parameters: parameters?.toList(growable: false), - ); + Future>> query( + String sql, [ + Iterable? parameters, + ]) async { + final results = await _innerRunSQL( + action: JsonQueryAction.queryRaw, + sql: sql, + parameters: parameters, + ); + + return deserializeRawResult(results[JsonQueryAction.queryRaw.name]); + } + + Future execute(String sql, [Iterable? parameters]) async { + final results = await _innerRunSQL( + action: JsonQueryAction.executeRaw, + sql: sql, + parameters: parameters, + ); - Future execute(String sql, [Iterable? parameters]) => - _inner( - action: JsonQueryAction.executeRaw, - sql: sql, - parameters: parameters?.toList(growable: false), - ); + return results[JsonQueryAction.executeRaw.name]; + } - Future _inner({ + Future _innerRunSQL({ required String sql, required JsonQueryAction action, - List? parameters, + Iterable? parameters, }) async { final args = { 'query': sql, - 'parameters': json.encode(rawParameter.encode(parameters ?? const [])), + 'parameters': serializeRawParams(parameters ?? const []), }; final query = serializeJsonQuery( @@ -39,12 +48,10 @@ class RawClient> { args: args, ); - final result = await _client.$engine.request( + return _client.$engine.request( query, headers: _client.$transaction.headers, transaction: _client.$transaction.transaction, ); - - return rawParameter.decode(result[action.name]); } } diff --git a/packages/orm/pubspec.yaml b/packages/orm/pubspec.yaml index 694c14e2..70d7f468 100644 --- a/packages/orm/pubspec.yaml +++ b/packages/orm/pubspec.yaml @@ -1,6 +1,6 @@ name: orm description: Next-generation ORM for Dart & Flutter | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB. -version: 5.0.7 +version: 5.1.0 homepage: https://prisma.pub repository: https://github.com/medz/prisma-dart funding: