Dart apis for the json1 extension (#235)

This commit is contained in:
Simon Binder 2019-11-28 19:13:51 +01:00
parent d2c863c5fc
commit 59f408229b
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 130 additions and 15 deletions

View File

@ -9,7 +9,8 @@
return ...
}).get();
```
- Provide Dart apis for the json1 extension in the `package:moor/extensions/json1.dart` library. Note that
json1 is not supported on most platforms.
- Batches are now always sent in a transaction, this used to be implementation specific before
## 2.1.0

View File

@ -0,0 +1,57 @@
/// Experimental bindings to the [json1](https://www.sqlite.org/json1.html)
/// sqlite extension.
///
/// Note that the json1 extension might not be available on all runtimes. In
/// particular, it can only work reliably on Android when using the
/// [moor_ffi](https://moor.simonbinder.eu/docs/other-engines/vm/) and it might
/// not work on older iOS versions.
@experimental
library json1;
import 'package:meta/meta.dart';
import '../moor.dart';
/// Defines extensions on string expressions to support the json1 api from Dart.
extension JsonExtensions on Expression<String, StringType> {
/// Assuming that this string is a json array, returns the length of this json
/// array.
///
/// The [path] parameter is optional. If it's set, it must refer to a valid
/// path in this json that will be used instead of `this`. See the
/// [sqlite documentation](https://www.sqlite.org/json1.html#path_arguments)
/// for details. If [path] is an invalid path, this expression can cause an
/// error when run by sqlite.
///
/// For this method to be valid, `this` must be a string representing a valid
/// json array. Otherwise, sqlite will report an error when attempting to
/// evaluate this expression.
///
/// See also:
/// - the [sqlite documentation for this function](https://www.sqlite.org/json1.html#the_json_array_length_function)
Expression<int, IntType> jsonArrayLength([String path]) {
return FunctionCallExpression('json_array_length', [
this,
if (path != null) Variable.withString(path),
]);
}
/// Assuming that this string is a json object or array, extracts a part of
/// this structure identified by [path].
///
/// For more details on how to format the [path] argument, see the
/// [sqlite documentation](https://www.sqlite.org/json1.html#path_arguments).
///
/// Evaluating this expression will cause an error if [path] has an invalid
/// format or `this` isn't well-formatted json.
///
/// Note that, since the return type of this function is dynamic, it can't be
/// used in [JoinedSelectStatement.addColumns]. It's also recommended to use
/// [Expression.equalsExp] with an explicit [Variable] instead of
/// [Expression.equals].
Expression jsonExtract(String path) {
return FunctionCallExpression('json_extract', [
this,
Variable.withString(path),
]);
}
}

View File

@ -47,10 +47,7 @@ class UseMoor {
/// Defines the `.moor` files to include when building the table structure for
/// this database. For details on how to integrate `.moor` files into your
/// Dart code, see [the documentation](https://moor.simonbinder.eu/docs/using-sql/custom_tables/).
///
/// Please note that this feature is experimental at the moment.
/// {@endtemplate}
@experimental
final Set<String> include;
/// Use this class as an annotation to inform moor_generator that a database
@ -59,7 +56,7 @@ class UseMoor {
@required this.tables,
this.daos = const [],
this.queries = const {},
@experimental this.include = const {},
this.include = const {},
});
}
@ -92,7 +89,6 @@ class UseDao {
final Map<String, String> queries;
/// {@macro moor_include_param}
@experimental
final Set<String> include;
/// Annotation for a class to declare it as an dao. See [UseDao] and the
@ -100,5 +96,5 @@ class UseDao {
const UseDao(
{@required this.tables,
this.queries = const {},
@experimental this.include = const {}});
this.include = const {}});
}

View File

@ -313,15 +313,24 @@ class _CastExpression<D1, D2, S1 extends SqlType<D1>, S2 extends SqlType<D2>>
}
}
class _FunctionCallExpression<R, S extends SqlType<R>>
extends Expression<R, S> {
/// A sql expression that calls a function.
///
/// This class is mainly used by moor internally. If you find yourself using
/// this class, consider [creating an issue](https://github.com/simolus3/moor/issues/new)
/// to request native support in moor.
class FunctionCallExpression<R, S extends SqlType<R>> extends Expression<R, S> {
/// The name of the function to call
final String functionName;
/// The arguments passed to the function, as expressions.
final List<Expression> arguments;
@override
final Precedence precedence = Precedence.primary;
_FunctionCallExpression(this.functionName, this.arguments);
/// Constructs a function call expression in sql from the [functionName] and
/// the target [arguments].
FunctionCallExpression(this.functionName, this.arguments);
@override
void writeInto(GenerationContext context) {
@ -345,7 +354,7 @@ class _FunctionCallExpression<R, S extends SqlType<R>>
@override
bool operator ==(other) {
return other is _FunctionCallExpression &&
return other is FunctionCallExpression &&
other.functionName == functionName &&
_equality.equals(other.arguments, arguments);
}

View File

@ -28,7 +28,7 @@ extension StringExpressionOperators on Expression<String, StringType> {
/// See also:
/// - https://www.w3resource.com/sqlite/core-functions-upper.php
Expression<String, StringType> upper() {
return _FunctionCallExpression('UPPER', [this]);
return FunctionCallExpression('UPPER', [this]);
}
/// Calls the sqlite function `LOWER` on `this` string. Please note that, in
@ -37,7 +37,7 @@ extension StringExpressionOperators on Expression<String, StringType> {
/// See also:
/// - https://www.w3resource.com/sqlite/core-functions-lower.php
Expression<String, StringType> lower() {
return _FunctionCallExpression('LOWER', [this]);
return FunctionCallExpression('LOWER', [this]);
}
/// Calls the sqlite function `LENGTH` on `this` string, which counts the
@ -47,7 +47,7 @@ extension StringExpressionOperators on Expression<String, StringType> {
/// See also:
/// - https://www.w3resource.com/sqlite/core-functions-length.php
Expression<int, IntType> get length {
return _FunctionCallExpression('LENGTH', [this]);
return FunctionCallExpression('LENGTH', [this]);
}
}

View File

@ -2,10 +2,14 @@ import 'package:moor/moor.dart';
import 'package:test/test.dart';
extension ComponentExpectations on Component {
void expectGenerates(String sql) {
void expectGenerates(String sql, [List<dynamic> variables]) {
final ctx = GenerationContext(SqlTypeSystem.defaultInstance, null);
writeInto(ctx);
expect(ctx.sql, sql);
if (variables != null) {
expect(ctx.boundVariables, variables);
}
}
}

View File

@ -0,0 +1,48 @@
import 'dart:convert';
import 'package:moor/moor.dart';
import 'package:moor/extensions/json1.dart';
import 'package:moor_ffi/moor_ffi.dart';
import 'package:test/test.dart';
import '../data/tables/todos.dart';
import '../data/utils/expect_generated.dart';
void main() {
test('json1 functions generate valid sql', () {
final column = GeneratedTextColumn('col', 'tbl', false);
column.jsonArrayLength().expectGenerates('json_array_length(col)');
column
.jsonArrayLength(r'$.c')
.expectGenerates('json_array_length(col, ?)', [r'$.c']);
column
.jsonExtract(r'$.c')
.expectGenerates('json_extract(col, ?)', [r'$.c']);
});
test('json1 integration test', () async {
final db = TodoDb(VmDatabase.memory());
const jsonObject = {
'foo': 'bar',
'array': [
'one',
'two',
'three',
],
};
await db
.into(db.pureDefaults)
.insert(PureDefaultsCompanion(txt: Value(json.encode(jsonObject))));
final arrayLengthExpr = db.pureDefaults.txt.jsonArrayLength(r'$.array');
final query = db.select(db.pureDefaults).addColumns([arrayLengthExpr]);
query.where(db.pureDefaults.txt
.jsonExtract(r'$.foo')
.equalsExp(Variable.withString('bar')));
final resultRow = await query.getSingle();
expect(resultRow.read(arrayLengthExpr), 3);
});
}