@@ -417,6 +417,99 @@ public function testRegexpReplaceEmptyReplacement() {
417417 $ this ->assertSame ( 'ac ' , $ this ->engine ->get_query_results ()[0 ]->r );
418418 }
419419
420+ /**
421+ * @dataProvider regexpSubstrCases
422+ */
423+ public function testRegexpSubstr ( $ sql , $ expected ) {
424+ $ this ->assertQuery ( "SELECT $ sql AS r " );
425+ $ this ->assertSame ( $ expected , $ this ->engine ->get_query_results ()[0 ]->r );
426+ }
427+
428+ public static function regexpSubstrCases () {
429+ return array (
430+ 'basic match ' => array ( "REGEXP_SUBSTR('abc123def', '[0-9]+') " , '123 ' ),
431+ 'no match ' => array ( "REGEXP_SUBSTR('abcdef', '[0-9]+') " , null ),
432+ 'pos ' => array ( "REGEXP_SUBSTR('abc123def456', '[0-9]+', 5) " , '23 ' ),
433+ 'pos with occurrence=2 ' => array ( "REGEXP_SUBSTR('abc123def456', '[0-9]+', 5, 2) " , '456 ' ),
434+ 'occurrence ' => array ( "REGEXP_SUBSTR('a1 b2 c3', '[a-z][0-9]', 1, 2) " , 'b2 ' ),
435+ 'occurrence too high ' => array ( "REGEXP_SUBSTR('a1 b2', '[a-z][0-9]', 1, 5) " , null ),
436+ 'match_type c ' => array ( "REGEXP_SUBSTR('ABC', 'abc', 1, 1, 'c') " , null ),
437+ 'multibyte match ' => array ( "REGEXP_SUBSTR('café', 'é') " , 'é ' ),
438+ 'null expr ' => array ( 'REGEXP_SUBSTR(NULL, \'abc \') ' , null ),
439+ 'null pattern ' => array ( "REGEXP_SUBSTR('abc', NULL) " , null ),
440+ );
441+ }
442+
443+ public function testRegexpSubstrNullPos () {
444+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('abc', 'a', NULL) AS r " );
445+ $ this ->assertNull ( $ this ->engine ->get_query_results ()[0 ]->r );
446+ }
447+
448+ public function testRegexpSubstrNullOccurrence () {
449+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('abc', 'a', 1, NULL) AS r " );
450+ $ this ->assertNull ( $ this ->engine ->get_query_results ()[0 ]->r );
451+ }
452+
453+ public function testRegexpSubstrNullMatchType () {
454+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('abc', 'a', 1, 1, NULL) AS r " );
455+ $ this ->assertNull ( $ this ->engine ->get_query_results ()[0 ]->r );
456+ }
457+
458+ public function testRegexpSubstrOccurrenceClampedToOne () {
459+ // MySQL clamps occurrence <= 0 to 1.
460+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('abcabc', 'b', 1, 0) AS r " );
461+ $ this ->assertSame ( 'b ' , $ this ->engine ->get_query_results ()[0 ]->r );
462+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('abcabc', 'b', 1, -5) AS r " );
463+ $ this ->assertSame ( 'b ' , $ this ->engine ->get_query_results ()[0 ]->r );
464+ }
465+
466+ public function testRegexpSubstrPosOutOfRange () {
467+ $ this ->assertQueryError (
468+ "SELECT REGEXP_SUBSTR('abc', 'a', 10) " ,
469+ 'Index out of bounds in regular expression search. '
470+ );
471+ }
472+
473+ public function testRegexpSubstrPosAtEnd () {
474+ // MySQL allows pos = char_count + 1 for SUBSTR; returns NULL.
475+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('abc', 'a', 4) AS r " );
476+ $ this ->assertNull ( $ this ->engine ->get_query_results ()[0 ]->r );
477+ }
478+
479+ public function testRegexpSubstrPosBeyondEnd () {
480+ $ this ->assertQueryError (
481+ "SELECT REGEXP_SUBSTR('abc', 'a', 5) " ,
482+ 'Index out of bounds in regular expression search. '
483+ );
484+ }
485+
486+ public function testRegexpSubstrPosZero () {
487+ $ this ->assertQueryError (
488+ "SELECT REGEXP_SUBSTR('abc', 'a', 0) " ,
489+ 'Index out of bounds in regular expression search. '
490+ );
491+ }
492+
493+ public function testRegexpSubstrInvalidPattern () {
494+ $ this ->assertQueryError (
495+ "SELECT REGEXP_SUBSTR('abc', '(abc') " ,
496+ 'Invalid regular expression: (abc. '
497+ );
498+ }
499+
500+ public function testRegexpSubstrInvalidFlag () {
501+ $ this ->assertQueryError (
502+ "SELECT REGEXP_SUBSTR('abc', 'a', 1, 1, 'x') " ,
503+ 'Invalid match_type flag: x. '
504+ );
505+ }
506+
507+ public function testRegexpSubstrLookbehindAcrossPos () {
508+ // The lookbehind sees bytes before pos because the full subject is kept.
509+ $ this ->assertQuery ( "SELECT REGEXP_SUBSTR('ab', '(?<=a)b', 2) AS r " );
510+ $ this ->assertSame ( 'b ' , $ this ->engine ->get_query_results ()[0 ]->r );
511+ }
512+
420513 public function testInsertDateNow () {
421514 $ this ->assertQuery (
422515 "INSERT INTO _dates (option_name, option_value) VALUES ('first', now()); "
0 commit comments