Support flags in regexp (#644)

This commit is contained in:
Simon Binder 2020-06-22 22:20:43 +02:00
parent bccbb3e7a8
commit d881659db6
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 196 additions and 10 deletions

View File

@ -2,6 +2,9 @@
- It's now possible to change the name of a class generated for moor-file queries. See
[the documentation](https://moor.simonbinder.eu/docs/using-sql/moor_files/#result-class-names) for details.
- Added the `multiLine`, `caseSensitive`, `unicode` and `doAll` flags to the `regex` sql extension
method.
They require the `moor_ffi` version `0.7.0` or later.
## 3.1.0

View File

@ -11,9 +11,49 @@ extension StringExpressionOperators on Expression<String> {
/// Matches this string against the regular expression in [regex].
///
/// Note that this function is only available when using `moor_ffi`. If
/// possible, consider using [like] instead.
Expression<bool> regexp(String regex) {
/// The [multiLine], [caseSensitive], [unicode] and [dotAll] parameters
/// correspond to the parameters on [RegExp].
///
/// Note that this function is only available when using `moor_ffi`. If you
/// need to support the web or `moor_flutter`, consider using [like] instead.
Expression<bool> regexp(
String regex, {
bool multiLine = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false,
}) {
// moor_ffi has a special regexp sql function that takes a third parameter
// to encode flags. If the least significant bit is set, multiLine is
// enabled. The next three bits enable case INSENSITIVITY (it's sensitive
// by default), unicode and dotAll.
var flags = 0;
if (multiLine) {
flags |= 1;
}
if (!caseSensitive) {
flags |= 2;
}
if (unicode) {
flags |= 4;
}
if (dotAll) {
flags |= 8;
}
if (flags != 0) {
return FunctionCallExpression<bool>(
'regexp_moor_ffi',
[
Variable.withString(regex),
this,
Variable.withInt(flags),
],
);
}
// No special flags enabled, use the regular REGEXP operator
return _LikeOperator(this, Variable.withString(regex), operator: 'REGEXP');
}

View File

@ -1,6 +1,6 @@
@TestOn('vm')
import 'package:moor/moor.dart';
import 'package:moor/extensions/moor_ffi.dart';
import 'package:moor/src/runtime/query_builder/query_builder.dart';
import 'package:moor_ffi/moor_ffi.dart';
import 'package:test/test.dart';
@ -56,4 +56,115 @@ void main() {
completion(isTrue),
);
});
group('regexp flags', () {
TodoDb db;
setUp(() async {
db = TodoDb(VmDatabase.memory());
// insert exactly one row so that we can evaluate expressions from Dart
await db.into(db.pureDefaults).insert(PureDefaultsCompanion.insert());
});
tearDown(() => db.close());
Future<bool> evaluate(Expression<bool> expr) async {
final result = await (db.selectOnly(db.pureDefaults)..addColumns([expr]))
.getSingle();
return result.read(expr);
}
test('multiLine', () {
expect(
evaluate(
Variable.withString('foo\nbar').regexp(
'^bar',
multiLine: true,
),
),
completion(isTrue),
);
expect(
evaluate(
Variable.withString('foo\nbar').regexp(
'^bar',
// multiLine is disabled by default
),
),
completion(isFalse),
);
});
test('caseSensitive', () {
expect(
evaluate(
Variable.withString('FOO').regexp(
'foo',
caseSensitive: false,
),
),
completion(isTrue),
);
expect(
evaluate(
Variable.withString('FOO').regexp(
'foo',
// caseSensitive should be true by default
),
),
completion(isFalse),
);
});
test('unicode', () {
// Note: `𝌆` is U+1D306 TETRAGRAM FOR CENTRE, an astral symbol.
// https://mathiasbynens.be/notes/es6-unicode-regex
const input = 'a𝌆b';
expect(
evaluate(
Variable.withString(input).regexp(
'a.b',
unicode: true,
),
),
completion(isTrue),
);
expect(
evaluate(
Variable.withString(input).regexp(
'a.b',
// Unicode is off by default
),
),
completion(isFalse),
);
});
test('dotAll', () {
expect(
evaluate(
Variable.withString('fo\n').regexp(
'fo.',
dotAll: true,
),
),
completion(isTrue),
);
expect(
evaluate(
Variable.withString('fo\n').regexp(
'fo.',
dotAll: false,
),
),
completion(isFalse),
);
});
});
}

View File

@ -71,7 +71,7 @@ extension SqliteValuePointer on Pointer<SqliteValue> {
/// - a [double]
/// - `null`
///
/// For texts and bytes, the value be copied.
/// For texts and bytes, the value will be copied.
dynamic get value {
final api = bindings;

View File

@ -69,8 +69,13 @@ void _atanImpl(Pointer<FunctionContext> ctx, int argCount,
void _regexpImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
if (argCount != 2) {
ctx.resultError('Expected two arguments to regexp');
var multiLine = false;
var caseSensitive = true;
var unicode = false;
var dotAll = false;
if (argCount < 2 || argCount > 3) {
ctx.resultError('Expected two or three arguments to regexp');
return;
}
@ -81,15 +86,32 @@ void _regexpImpl(Pointer<FunctionContext> ctx, int argCount,
ctx.resultNull();
return;
}
if (firstParam is! String || secondParam is! String) {
ctx.resultError('Expected two strings as parameters to regexp');
return;
}
if (argCount == 3) {
// In the variant with three arguments, the last (int) arg can be used to
// enable regex flags. See the regexp() extension in moor for details.
final value = args[2].value;
if (value is int) {
multiLine = (value & 1) == 1;
caseSensitive = (value & 2) != 2;
unicode = (value & 4) == 4;
dotAll = (value & 8) == 8;
}
}
RegExp regex;
try {
regex = RegExp(firstParam as String);
regex = RegExp(
firstParam as String,
multiLine: multiLine,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll,
);
} on FormatException catch (e) {
ctx.resultError('Invalid regex: $e');
return;
@ -149,6 +171,9 @@ void _registerOn(Database db) {
db.createFunction('regexp', 2, Pointer.fromFunction(_regexpImpl),
isDeterministic: true);
// Third argument can be used to set flags (like multiline, case sensitivity,
// etc.)
db.createFunction('regexp_moor_ffi', 3, Pointer.fromFunction(_regexpImpl));
final containsImplPointer =
Pointer.fromFunction<sqlite3_function_handler>(_containsImpl);

View File

@ -1,6 +1,6 @@
name: moor_ffi
description: "Provides sqlite bindings using dart:ffi, including a moor executor"
version: 0.6.0
version: 0.7.0-dev
homepage: https://github.com/simolus3/moor/tree/develop/moor_ffi
issue_tracker: https://github.com/simolus3/moor/issues

View File

@ -109,6 +109,13 @@ void main() {
expect(result.single['r'], 0);
});
test('supports flags', () {
final stmt =
db.prepare(r"SELECT regexp_moor_ffi('^bar', 'foo\nbar', 8) AS r;");
final result = stmt.select();
expect(result.single['r'], 0);
});
test('returns null when either argument is null', () {
final stmt = db.prepare('SELECT ? REGEXP ?');