From d881659db62c7aaff7e02c91e8c7960d5c026060 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 22 Jun 2020 22:20:43 +0200 Subject: [PATCH] Support flags in regexp (#644) --- moor/CHANGELOG.md | 3 + .../query_builder/expressions/text.dart | 46 ++++++- .../extensions/moor_ffi_functions_test.dart | 113 +++++++++++++++++- moor_ffi/lib/src/bindings/types.dart | 2 +- moor_ffi/lib/src/impl/moor_functions.dart | 33 ++++- moor_ffi/pubspec.yaml | 2 +- .../test/database/moor_functions_test.dart | 7 ++ 7 files changed, 196 insertions(+), 10 deletions(-) diff --git a/moor/CHANGELOG.md b/moor/CHANGELOG.md index 53f61ddc..376deb17 100644 --- a/moor/CHANGELOG.md +++ b/moor/CHANGELOG.md @@ -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 diff --git a/moor/lib/src/runtime/query_builder/expressions/text.dart b/moor/lib/src/runtime/query_builder/expressions/text.dart index cdb8b2f2..604f5bc1 100644 --- a/moor/lib/src/runtime/query_builder/expressions/text.dart +++ b/moor/lib/src/runtime/query_builder/expressions/text.dart @@ -11,9 +11,49 @@ extension StringExpressionOperators on Expression { /// 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 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 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( + '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'); } diff --git a/moor/test/extensions/moor_ffi_functions_test.dart b/moor/test/extensions/moor_ffi_functions_test.dart index 71637b43..3d69649d 100644 --- a/moor/test/extensions/moor_ffi_functions_test.dart +++ b/moor/test/extensions/moor_ffi_functions_test.dart @@ -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 evaluate(Expression 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), + ); + }); + }); } diff --git a/moor_ffi/lib/src/bindings/types.dart b/moor_ffi/lib/src/bindings/types.dart index 3fe921e1..f92728c8 100644 --- a/moor_ffi/lib/src/bindings/types.dart +++ b/moor_ffi/lib/src/bindings/types.dart @@ -71,7 +71,7 @@ extension SqliteValuePointer on Pointer { /// - 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; diff --git a/moor_ffi/lib/src/impl/moor_functions.dart b/moor_ffi/lib/src/impl/moor_functions.dart index ddecf33a..e33ee9f8 100644 --- a/moor_ffi/lib/src/impl/moor_functions.dart +++ b/moor_ffi/lib/src/impl/moor_functions.dart @@ -69,8 +69,13 @@ void _atanImpl(Pointer ctx, int argCount, void _regexpImpl(Pointer ctx, int argCount, Pointer> 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 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(_containsImpl); diff --git a/moor_ffi/pubspec.yaml b/moor_ffi/pubspec.yaml index 620b3a5a..887c3d38 100644 --- a/moor_ffi/pubspec.yaml +++ b/moor_ffi/pubspec.yaml @@ -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 diff --git a/moor_ffi/test/database/moor_functions_test.dart b/moor_ffi/test/database/moor_functions_test.dart index a829c326..c6c29962 100644 --- a/moor_ffi/test/database/moor_functions_test.dart +++ b/moor_ffi/test/database/moor_functions_test.dart @@ -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 ?');