mirror of https://github.com/AMT-Cheif/drift.git
Support jsonb functions in query builder
This commit is contained in:
parent
c4169f6f91
commit
2a2990e362
|
@ -8,6 +8,7 @@
|
|||
- Close wasm databases hosted in workers after the last client disconnects.
|
||||
- Add `enableMigrations` parameter to `NativeDatabase` which can be used to
|
||||
optionally disable database migrations when opening databases.
|
||||
- Support `jsonb` functions in the Dart query builder.
|
||||
|
||||
## 2.14.1
|
||||
|
||||
|
|
|
@ -12,6 +12,22 @@ import '../drift.dart';
|
|||
|
||||
/// Defines extensions on string expressions to support the json1 api from Dart.
|
||||
extension JsonExtensions on Expression<String> {
|
||||
/// Reads `this` expression as a JSON structure and outputs the JSON in a
|
||||
/// minified format.
|
||||
///
|
||||
/// For details, see https://www.sqlite.org/json1.html#jmini.
|
||||
Expression<String> json() {
|
||||
return FunctionCallExpression('json', [this]);
|
||||
}
|
||||
|
||||
/// Reads `this` expression as a JSON structure and outputs the JSON in a
|
||||
/// binary format internal to sqlite3.
|
||||
///
|
||||
/// For details, see https://www.sqlite.org/json1.html#jminib.
|
||||
Expression<Uint8List> jsonb() {
|
||||
return FunctionCallExpression('jsonb', [this]);
|
||||
}
|
||||
|
||||
/// Assuming that this string is a json array, returns the length of this json
|
||||
/// array.
|
||||
///
|
||||
|
@ -85,6 +101,56 @@ extension JsonExtensions on Expression<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Defines extensions for the binary `JSONB` format introduced in sqlite3
|
||||
/// version 3.45.
|
||||
///
|
||||
/// For details, see https://www.sqlite.org/json1.html#jsonb
|
||||
extension JsonbExtensions on Expression<Uint8List> {
|
||||
/// Reads this binary JSONB structure and emits its textual representation as
|
||||
/// minified JSON.
|
||||
///
|
||||
/// For details, see https://www.sqlite.org/json1.html#jmini.
|
||||
Expression<String> json() {
|
||||
return dartCast<String>().json();
|
||||
}
|
||||
|
||||
/// Assuming that `this` is an expression evaluating to a binary JSONB array,
|
||||
/// returns the length of the array.
|
||||
///
|
||||
/// See [JsonExtensions.jsonArrayLength] for more details and
|
||||
/// https://www.sqlite.org/json1.html#jsonb for details on jsonb.
|
||||
Expression<int> jsonArrayLength([String? path]) {
|
||||
// the function accepts both formats, and this way we avoid some duplicate
|
||||
// code.
|
||||
return dartCast<String>().jsonArrayLength(path);
|
||||
}
|
||||
|
||||
/// Assuming that `this` is an expression evaluating to a binary JSONB object
|
||||
/// or array, extracts the part of the structure identified by [path].
|
||||
///
|
||||
/// For more details, see [JsonExtensions.jsonExtract] or
|
||||
/// https://www.sqlite.org/json1.html#jex.
|
||||
Expression<T> jsonExtract<T extends Object>(String path) {
|
||||
return dartCast<String>().jsonExtract(path);
|
||||
}
|
||||
|
||||
/// Calls the `json_each` table-valued function on `this` binary JSON buffer,
|
||||
/// optionally using [path] as the root path.
|
||||
///
|
||||
/// See [JsonTableFunction] and [JsonExtensions.jsonEach] for more details.
|
||||
JsonTableFunction jsonEach(DatabaseConnectionUser database, [String? path]) {
|
||||
return dartCast<String>().jsonEach(database, path);
|
||||
}
|
||||
|
||||
/// Calls the `json_tree` table-valued function on `this` binary JSON buffer,
|
||||
/// optionally using [path] as the root path.
|
||||
///
|
||||
/// See [JsonTableFunction] and [JsonExtensions.jsonTree] for more details.
|
||||
JsonTableFunction jsonTree(DatabaseConnectionUser database, [String? path]) {
|
||||
return dartCast<String>().jsonTree(database, path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls [json table-valued functions](https://sqlite.org/json1.html#jeach) in
|
||||
/// drift.
|
||||
///
|
||||
|
|
|
@ -70,9 +70,38 @@ void main() {
|
|||
|
||||
expect(rows, [
|
||||
(DriftAny('bar'), 0),
|
||||
(DriftAny('one'), 4),
|
||||
(DriftAny('two'), 4),
|
||||
(DriftAny('three'), 4),
|
||||
(DriftAny('one'), 10),
|
||||
(DriftAny('two'), 10),
|
||||
(DriftAny('three'), 10),
|
||||
]);
|
||||
});
|
||||
|
||||
group('jsonb', () {
|
||||
setUp(() async {
|
||||
await db.categories
|
||||
.insertOne(CategoriesCompanion.insert(description: '_'));
|
||||
});
|
||||
|
||||
Expression<Uint8List> jsonb(Object? dart) {
|
||||
return Variable(json.encode(dart)).jsonb();
|
||||
}
|
||||
|
||||
Future<T?> eval<T extends Object>(Expression<T> expr) {
|
||||
final query = db.selectOnly(db.categories)..addColumns([expr]);
|
||||
return query.getSingle().then((row) => row.read(expr));
|
||||
}
|
||||
|
||||
test('json', () async {
|
||||
expect(await eval(jsonb([1, 2, 3]).json()), '[1,2,3]');
|
||||
});
|
||||
|
||||
test('jsonArrayLength', () async {
|
||||
expect(await eval(jsonb([1, 2, 3]).jsonArrayLength()), 3);
|
||||
});
|
||||
|
||||
test('jsonExtract', () async {
|
||||
expect(
|
||||
await eval(jsonb(jsonObject).jsonExtract<String>(r'$.foo')), 'bar');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ import 'package:drift/drift.dart';
|
|||
import 'package:drift/extensions/json1.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../generated/todos.dart';
|
||||
import '../test_utils/test_utils.dart';
|
||||
|
||||
void main() {
|
||||
test('json1 functions generate valid sql', () {
|
||||
const column = CustomExpression<String>('col');
|
||||
const column = CustomExpression<String>('col');
|
||||
const binary = CustomExpression<Uint8List>('bin');
|
||||
|
||||
test('json1 functions generate valid sql', () {
|
||||
expect(column.jsonArrayLength(), generates('json_array_length(col)'));
|
||||
expect(
|
||||
column.jsonArrayLength(r'$.c'),
|
||||
|
@ -19,4 +21,70 @@ void main() {
|
|||
generates('json_extract(col, ?)', [r'$.c']),
|
||||
);
|
||||
});
|
||||
|
||||
group('textual', () {
|
||||
test('json', () {
|
||||
expect(column.json(), generates('json(col)'));
|
||||
});
|
||||
|
||||
test('jsonb', () {
|
||||
expect(column.jsonb(), generates('jsonb(col)'));
|
||||
});
|
||||
|
||||
test('jsonArrayLength', () {
|
||||
expect(column.jsonArrayLength(), generates('json_array_length(col)'));
|
||||
});
|
||||
|
||||
test('jsonExtract', () {
|
||||
expect(column.jsonExtract(r'$.c'),
|
||||
generates(r'json_extract(col, ?)', [r'$.c']));
|
||||
});
|
||||
|
||||
test('jsonEach', () async {
|
||||
final db = TodoDb();
|
||||
addTearDown(db.close);
|
||||
|
||||
final query = db.select(Variable.withString('{}').jsonEach(db));
|
||||
expect(query, generates('SELECT * FROM json_each(?)', [anything]));
|
||||
});
|
||||
|
||||
test('jsonTree', () async {
|
||||
final db = TodoDb();
|
||||
addTearDown(db.close);
|
||||
|
||||
final query = db.select(Variable.withString('{}').jsonTree(db));
|
||||
expect(query, generates('SELECT * FROM json_tree(?)', [anything]));
|
||||
});
|
||||
});
|
||||
|
||||
group('binary', () {
|
||||
test('json', () {
|
||||
expect(column.jsonb().json(), generates('json(jsonb(col))'));
|
||||
});
|
||||
|
||||
test('jsonArrayLength', () {
|
||||
expect(binary.jsonArrayLength(), generates('json_array_length(bin)'));
|
||||
});
|
||||
|
||||
test('jsonExtract', () {
|
||||
expect(binary.jsonExtract(r'$.c'),
|
||||
generates(r'json_extract(bin, ?)', [r'$.c']));
|
||||
});
|
||||
|
||||
test('jsonEach', () async {
|
||||
final db = TodoDb();
|
||||
addTearDown(db.close);
|
||||
|
||||
final query = db.select(Variable.withBlob(Uint8List(0)).jsonEach(db));
|
||||
expect(query, generates('SELECT * FROM json_each(?)', [anything]));
|
||||
});
|
||||
|
||||
test('jsonTree', () async {
|
||||
final db = TodoDb();
|
||||
addTearDown(db.close);
|
||||
|
||||
final query = db.select(Variable.withBlob(Uint8List(0)).jsonTree(db));
|
||||
expect(query, generates('SELECT * FROM json_tree(?)', [anything]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import 'package:archive/archive_io.dart';
|
|||
import 'package:http/http.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
const _version = '3410000';
|
||||
const _year = '2023';
|
||||
const _version = '3450000';
|
||||
const _year = '2024';
|
||||
const _url = 'https://www.sqlite.org/$_year/sqlite-autoconf-$_version.tar.gz';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
|
|
Loading…
Reference in New Issue