From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 44E8327B9C for ; Fri, 27 Jul 2018 15:11:49 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id kMQf_vwFYPcb for ; Fri, 27 Jul 2018 15:11:49 -0400 (EDT) Received: from mail-lj1-f171.google.com (mail-lj1-f171.google.com [209.85.208.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 7ECC527B95 for ; Fri, 27 Jul 2018 15:11:48 -0400 (EDT) Received: by mail-lj1-f171.google.com with SMTP id y17-v6so5297843ljy.8 for ; Fri, 27 Jul 2018 12:11:48 -0700 (PDT) MIME-Version: 1.0 References: <1530190036-10105-1-git-send-email-hollow653@gmail.com> <20180718024314.be245cmsgklxuvnk@tkn_work_nb> <20180727130601.b2oby7dleapd5upg@tkn_work_nb> In-Reply-To: <20180727130601.b2oby7dleapd5upg@tkn_work_nb> From: Nikita Tatunov Date: Fri, 27 Jul 2018 22:11:35 +0300 Message-ID: Subject: [tarantool-patches] Re: [PATCH] sql: LIKE & GLOB pattern comparison issue Content-Type: multipart/alternative; boundary="0000000000001245790571ffe284" Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-help: List-unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-subscribe: List-owner: List-post: List-archive: To: Alexander Turenko Cc: tarantool-patches@freelists.org --0000000000001245790571ffe284 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hello! =D0=BF=D1=82, 27 =D0=B8=D1=8E=D0=BB. 2018 =D0=B3. =D0=B2 16:06, Alexander T= urenko < alexander.turenko@tarantool.org>: > Hi, > > See comments below. > > Don't sure we should return 'no match' situation when a left hand > expression in not correct unicode string. How other DBs handle that? > > The main point is that since pattern is always valid (otherwise we return an error) it cannot be matched with a string that contains an invalid utf-8 character= . - PostgreSQL doesn't allow storing invalid characters in text types and even explicit E'\xD1' anywhere results an error. - MySQL Doesn't allow using invalid characters in text types. May be it's reasons for thinking about prohibiting invalid characters in text? WBR, Alexander Turenko. > > > sql: LIKE & GLOB pattern comparison issue > > > > Currently function that compares pattern and string for GLOB & LIKE > > operators doesn't work properly. It uses ICU reading function which > > perhaps was working differently before and the implementation for the > > comparison ending isn't paying attention to some special cases, hence > > in those cases it works improperly. > > > > It used non-ICU function before, consider commit 198d59ce. > > Thank you, I've got it now. > > With the patch applied an error will be returned in case there's > > invalid UTF-8 symbol in pattern & pattern will not be matched > > with the string that contains it. Some minor corrections to function > > were made as well. > > > > Nitpicking: 'error will be returned' is third situatuon, other then > 'matched' and 'not matched'. > > I guess commit message is about changes. Do I really need to mention valid cases that didn't change? > > =D0=A1loses #3251 > > =D0=A1loses #3334 > > Part of #3572 > > > > 2. The thing you test is not related to a table and other columns. > > > Please, convert the tests to the next format: {[[select '' like > > > '_B';]], {1}]]}. > > > To make it more readable, you can do it like `like_testcases` in > > > `sql-tap/collation.test.lua`. > > > > > > > Done. > > You are still perform comparisons with table columns. > Thank you, fixed it. More tests now tho. > > > -/* > > - * For LIKE and GLOB matching on EBCDIC machines, assume that every > > - * character is exactly one byte in size. Also, provde the Utf8Read() > > - * macro for fast reading of the next character in the common case whe= re > > - * the next character is ASCII. > > +/** > > + * Providing there are symbols in string s this > > + * macro returns UTF-8 code of character and > > + * promotes pointer to the next symbol in the string. > > + * Otherwise return code is SQL_END_OF_STRING. > > */ > > -#define Utf8Read(s, e) ucnv_getNextUChar(pUtf8conv, &s, e, &status) > > +#define Utf8Read(s, e) (((s) < (e)) ?\ > > + ucnv_getNextUChar(pUtf8conv, &(s), (e), &(status)) : 0) > > I suggest to add a whitespace before backslash to make it looks better. > > Guess so. > > @@ -643,51 +647,61 @@ static const struct compareInfo likeInfoAlt =3D { > '%', > > '_', 0, 0 }; > > #define SQLITE_MATCH 0 > > #define SQLITE_NOMATCH 1 > > #define SQLITE_NOWILDCARDMATCH 2 > > +#define SQL_PROHIBITED_PATTERN 3 > > Update comment before that has a reference to 'patternMatch' function. > > Didn't notice it, thnx. > > * > > * Globbing rules: > > * > > - * '*' Matches any sequence of zero or more characters. > > + * '*' Matches any sequence of zero or more characters. > > Why? > > > * > > - * '?' Matches exactly one character. > > + * '?' Matches exactly one character. > > * > > - * [...] Matches one character from the enclosed list of > > - * characters. > > + * [...] Matches one character from the enclosed list of > > + * characters. > > * > > - * [^...] Matches one character not in the enclosed list. > > + * [^...] Matches one character not in the enclosed list. > > * > > The same question. > > It was looking like the indentation for similar table below was different. I was wrong. > > + * @retval SQLITE_MATCH: Match. > > + * SQLITE_NOMATCH: No match. > > + * SQLITE_NOWILDCARDMATCH: No match in spite of having * > > + * or % wildcards. > > + * SQL_PROHIBITED_PATTERN Pattern contains invalid > > + * symbol. > > Nitpicking: no colon after SQL_PROHIBITED_PATTERN. > Fixed. > > > */ > > static int > > -patternCompare(const char * pattern, /* The glob pattern */ > > - const char * string, /* The string to compare against the glob > */ > > - const struct compareInfo *pInfo, /* Information about how to d= o > > the compare */ > > - UChar32 matchOther /* The escape char (LIKE) or '[' (GLOB) */ > > - ) > > +sql_utf8_pattern_compare(const char * pattern, > > + const char * string, > > + const struct compareInfo *pInfo, > > + UChar32 matchOther) > > Don't get why indent is tabulation + 7 spaces. > > Me neither :thinking:. Thank you! > The function itself looks good except lines longer then 80 columns. > As i said before I don't think breaking these lines will increase readability. > > @@ -853,8 +903,7 @@ patternCompare(const char * pattern, /* The glob > > pattern */ > > int > > sqlite3_strglob(const char *zGlobPattern, const char *zString) > > { > > - return patternCompare(zGlobPattern, zString, &globInfo, > > - '['); > > + return sql_utf8_pattern_compare(zGlobPattern, zString, &globInfo, '['= ); > > } > > > > /* > > @@ -864,7 +913,7 @@ sqlite3_strglob(const char *zGlobPattern, const cha= r > > *zString) > > int > > sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int > esc) > > { > > - return patternCompare(zPattern, zStr, &likeInfoNorm, esc); > > + return sql_utf8_pattern_compare(zPattern, zStr, &likeInfoNorm, esc); > > } > > > > Should we add sqlite3_result_error is case of SQL_PROHIBITED_PATTERN for > these two functions? > > It doesn't matter for "sqlite3_strglob()", since it's only used internally with valid explicitly given pattern in "analysis_loader()". Same for "sqlite3_strlike()". > > - {"LIKE", "like"}, > > - {"GLOB", "glob"}, > > +-- NOTE: This test needs refactoring after deletion of GLOB & > > +-- type restrictions for LIKE. > > +-- {"LIKE", "like"}, > > +-- {"GLOB", "glob"}, > > I would add related issue number. > > True. Done. > > {"AND", "and"}, > > {"OR", "or"}, > > {"MATCH", "match"}, > > @@ -96,7 +98,7 @@ operations =3D { > > {"+", "-"}, > > {"<<", ">>", "&", "|"}, > > {"<", "<=3D", ">", ">=3D"}, > > - {"=3D", "=3D=3D", "!=3D", "<>", "LIKE", "GLOB"}, --"MATCH", "REGEX= P"}, > > + {"=3D", "=3D=3D", "!=3D", "<>"}, --"LIKE", "GLOB"}, "MATCH", "REGE= XP"}, > > Leave comment before MATCH and REGEXP to avoid surprises after removing > your comment. > > Done. > > --- /dev/null > > +++ b/test/sql-tap/gh-3251-string-pattern-comparison.lua > > @@ -0,0 +1,238 @@ > > +#!/usr/bin/env tarantool > > +test =3D require("sqltester") > > +test:plan(106) > > + > > +local prefix =3D "like-test-" > > + > > +-- Unciode byte sequences. > > + > > 1. Typo: unicode. > 2. Extra empty line. > > Fixed. > > +-- Invalid testcases. > > + > > +for i, tested_string in ipairs(invalid_testcases) do > > + local test_name =3D prefix .. "2." .. tostring(i) > > + local test_itself =3D "SELECT 'abc' LIKE 'ab" .. tested_string .. "';= " > > + > > +-- We should raise an error if pattern contains invalid characters. > > + > > 1. Trailing whitespaces. > 2. Why comments are not indented as the other code? > 3. test_name and test_itself before the section comment looks strange > for me here. > diff --git a/src/box/sql/func.c b/src/box/sql/func.c index 5b53076..2f989d4 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -623,7 +623,7 @@ struct compareInfo { * promotes pointer to the next symbol in the string. * Otherwise return code is SQL_END_OF_STRING. */ -#define Utf8Read(s, e) (((s) < (e)) ?\ +#define Utf8Read(s, e) (((s) < (e)) ? \ ucnv_getNextUChar(pUtf8conv, &(s), (e), &(status)) : 0) #define SQL_END_OF_STRING 0 @@ -642,7 +642,7 @@ static const struct compareInfo likeInfoNorm =3D { '%', '_', 0, 1 }; static const struct compareInfo likeInfoAlt =3D { '%', '_', 0, 0 }; /* - * Possible error returns from patternMatch() + * Possible error returns from sql_utf8_pattern_compare() */ #define SQLITE_MATCH 0 #define SQLITE_NOMATCH 1 @@ -655,14 +655,14 @@ static const struct compareInfo likeInfoAlt =3D { '%'= , '_', 0, 0 }; * * Globbing rules: * - * '*' Matches any sequence of zero or more characters. + * '*' Matches any sequence of zero or more characters. * - * '?' Matches exactly one character. + * '?' Matches exactly one character. * - * [...] Matches one character from the enclosed list of - * characters. + * [...] Matches one character from the enclosed list of + * characters. * - * [^...] Matches one character not in the enclosed list. + * [^...] Matches one character not in the enclosed list. * * With the [...] and [^...] matching, a ']' character can be * included in the list by making it the first character after @@ -694,14 +694,14 @@ static const struct compareInfo likeInfoAlt =3D { '%'= , '_', 0, 0 }; * SQLITE_NOMATCH: No match. * SQLITE_NOWILDCARDMATCH: No match in spite of having * * or % wildcards. - * SQL_PROHIBITED_PATTERN Pattern contains invalid + * SQL_PROHIBITED_PATTERN: Pattern contains invalid * symbol. */ static int sql_utf8_pattern_compare(const char * pattern, - const char * string, - const struct compareInfo *pInfo, - UChar32 matchOther) + const char * string, + const struct compareInfo *pInfo, + UChar32 matchOther) { UChar32 c, c2; /* Next pattern and input string chars */ UChar32 matchOne =3D pInfo->matchOne; /* "?" or "_" */ diff --git a/test/sql-tap/e_expr.test.lua b/test/sql-tap/e_expr.test.lua index 051210a..9780d2c 100755 --- a/test/sql-tap/e_expr.test.lua +++ b/test/sql-tap/e_expr.test.lua @@ -78,7 +78,7 @@ local operations =3D { {"!=3D", "ne2"}, {"IS", "is"}, -- NOTE: This test needs refactoring after deletion of GLOB & --- type restrictions for LIKE. +-- type restrictions for LIKE. (See #3572) -- {"LIKE", "like"}, -- {"GLOB", "glob"}, {"AND", "and"}, @@ -98,7 +98,12 @@ operations =3D { {"+", "-"}, {"<<", ">>", "&", "|"}, {"<", "<=3D", ">", ">=3D"}, - {"=3D", "=3D=3D", "!=3D", "<>"}, --"LIKE", "GLOB"}, "MATCH", "REGEXP"}= , +-- NOTE: This test needs refactoring after deletion of GLOB & +-- type restrictions for LIKE. (See #3572) +-- Another NOTE: MATCH & REGEXP aren't supported in Tarantool & +-- are waiting for their hour, don't confuse them +-- being commented with ticket above. + {"=3D", "=3D=3D", "!=3D", "<>"}, --"LIKE", "GLOB"}, --"MATCH", "REGEXP= "}, {"AND"}, {"OR"}, } diff --git a/test/sql-tap/gh-3251-string-pattern-comparison.test.lua b/test/sql-tap/gh-3251-string-pattern-comparison.test.lua new file mode 100755 index 0000000..a823bc6 --- /dev/null +++ b/test/sql-tap/gh-3251-string-pattern-comparison.test.lua @@ -0,0 +1,281 @@ +#!/usr/bin/env tarantool +test =3D require("sqltester") +test:plan(128) + +local prefix =3D "like-test-" + +-- Unicode byte sequences. +local valid_testcases =3D { + '\x01', + '\x09', + '\x1F', + '\x7F', + '\xC2\x80', + '\xC2\x90', + '\xC2\x9F', + '\xE2\x80\xA8', + '\x20\x0B', + '\xE2\x80\xA9', +} + +-- Non-Unicode byte sequences. +local invalid_testcases =3D { + '\xE2\x80', + '\xFE\xFF', + '\xC2', + '\xED\xB0\x80', + '\xD0', +} + +local like_test_cases =3D +{ + {"1.1", + "SELECT 'AB' LIKE '_B';", + {0, {1}} }, + {"1.2", + "SELECT 'CD' LIKE '_B';", + {0, {0}} }, + {"1.3", + "SELECT '' LIKE '_B';", + {0, {0}} }, + {"1.4", + "SELECT 'AB' LIKE '%B';", + {0, {1}} }, + {"1.5", + "SELECT 'CD' LIKE '%B';", + {0, {0}} }, + {"1.6", + "SELECT '' LIKE '%B';", + {0, {0}} }, + {"1.7", + "SELECT 'AB' LIKE 'A__';", + {0, {0}} }, + {"1.8", + "SELECT 'CD' LIKE 'A__';", + {0, {0}} }, + {"1.9", + "SELECT '' LIKE 'A__';", + {0, {0}} }, + {"1.10", + "SELECT 'AB' LIKE 'A_';", + {0, {1}} }, + {"1.11", + "SELECT 'CD' LIKE 'A_';", + {0, {0}} }, + {"1.12", + "SELECT '' LIKE 'A_';", + {0, {0}} }, + {"1.13", + "SELECT 'AB' LIKE 'A';", + {0, {0}} }, + {"1.14", + "SELECT 'CD' LIKE 'A';", + {0, {0}} }, + {"1.15", + "SELECT '' LIKE 'A';", + {0, {0}} }, + {"1.16", + "SELECT 'AB' LIKE '_';", + {0, {0}} }, + {"1.17", + "SELECT 'CD' LIKE '_';", + {0, {0}} }, + {"1.18", + "SELECT '' LIKE '_';", + {0, {0}} }, + {"1.19", + "SELECT 'AB' LIKE '__';", + {0, {1}} }, + {"1.20", + "SELECT 'CD' LIKE '__';", + {0, {1}} }, + {"1.21", + "SELECT '' LIKE '__';", + {0, {0}} }, + {"1.22", + "SELECT 'AB' LIKE '%A';", + {0, {0}} }, + {"1.23", + "SELECT 'AB' LIKE '%C';", + {0, {0}} }, + {"1.24", + "SELECT 'ab' LIKE '%df';", + {0, {0}} }, + {"1.25", + "SELECT 'abCDF' LIKE '%df';", + {0, {1}} }, + {"1.26", + "SELECT 'CDF' LIKE '%df';", + {0, {1}} }, + {"1.27", + "SELECT 'ab' LIKE 'a_';", + {0, {1}} }, + {"1.28", + "SELECT 'abCDF' LIKE 'a_';", + {0, {0}} }, + {"1.29", + "SELECT 'CDF' LIKE 'a_';", + {0, {0}} }, + {"1.30", + "SELECT 'ab' LIKE 'ab%';", + {0, {1}} }, + {"1.31", + "SELECT 'abCDF' LIKE 'ab%';", + {0, {1}} }, + {"1.32", + "SELECT 'CDF' LIKE 'ab%';", + {0, {0}} }, + {"1.33", + "SELECT 'ab' LIKE 'abC%';", + {0, {0}} }, + {"1.34", + "SELECT 'abCDF' LIKE 'abC%';", + {0, {1}} }, + {"1.35", + "SELECT 'CDF' LIKE 'abC%';", + {0, {0}} }, + {"1.36", + "SELECT 'ab' LIKE 'a_%';", + {0, {1}} }, + {"1.37", + "SELECT 'abCDF' LIKE 'a_%';", + {0, {1}} }, + {"1.38", + "SELECT 'CDF' LIKE 'a_%';", + {0, {0}} }, +} + +test:do_catchsql_set_test(like_test_cases, prefix) + +-- Invalid testcases. + +for i, tested_string in ipairs(invalid_testcases) do + +-- We should raise an error if pattern contains invalid characters. + local test_name =3D prefix .. "2." .. tostring(i) + local test_itself =3D "SELECT 'abc' LIKE 'ab" .. tested_string .. "';" + test:do_catchsql_test( + test_name, + test_itself, { + -- + 1, "LIKE or GLOB pattern can only contain UTF-8 characters" + -- + }) + + test_name =3D prefix .. "3." .. tostring(i) + test_itself =3D "SELECT 'abc' LIKE 'abc" .. tested_string .. "';" + test:do_catchsql_test( + test_name, + test_itself, { + -- + 1, "LIKE or GLOB pattern can only contain UTF-8 characters" + -- + }) + + test_name =3D prefix .. "4." .. tostring(i) + test_itself =3D "SELECT 'abc' LIKE 'ab" .. tested_string .. "c';" + test:do_catchsql_test( + test_name, + test_itself, { + -- + 1, "LIKE or GLOB pattern can only contain UTF-8 characters" + -- + }) + +-- Just skipping if row value predicand contains invalid character. + + test_name =3D prefix .. "5." .. tostring(i) + test_itself =3D "SELECT 'ab" .. tested_string .. "' LIKE 'abc';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + + test_name =3D prefix .. "6." .. tostring(i) + test_itself =3D "SELECT 'abc" .. tested_string .. "' LIKE 'abc';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + + test_name =3D prefix .. "7." .. tostring(i) + test_itself =3D "SELECT 'ab" .. tested_string .. "c' LIKE 'abc';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) +end + +-- Valid testcases. + +for i, tested_string in ipairs(valid_testcases) do + test_name =3D prefix .. "8." .. tostring(i) + local test_itself =3D "SELECT 'abc' LIKE 'ab" .. tested_string .. "';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + + test_name =3D prefix .. "9." .. tostring(i) + test_itself =3D "SELECT 'abc' LIKE 'abc" .. tested_string .. "';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + + test_name =3D prefix .. "10." .. tostring(i) + test_itself =3D "SELECT 'abc' LIKE 'ab" .. tested_string .. "c';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + test_name =3D prefix .. "11." .. tostring(i) + test_itself =3D "SELECT 'ab" .. tested_string .. "' LIKE 'abc';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + + test_name =3D prefix .. "12." .. tostring(i) + test_itself =3D "SELECT 'abc" .. tested_string .. "' LIKE 'abc';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) + + test_name =3D prefix .. "13." .. tostring(i) + test_itself =3D "SELECT 'ab" .. tested_string .. "c' LIKE 'abc';" + test:do_execsql_test( + test_name, + test_itself, { + -- + 0 + -- + }) +end + +test:finish_test() --0000000000001245790571ffe284 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hello!

= =D0=BF=D1=82, 27 =D0=B8=D1=8E=D0=BB. 2018 =D0=B3. =D0=B2 16:06, Alexander T= urenko <alexander.tur= enko@tarantool.org>:
Hi,

See comments below.

Don't sure we should return 'no match' situation when a left ha= nd
expression in not correct unicode string. How other DBs handle that?


The main point is that since pattern i= s always valid (otherwise we return an error)
it cannot be matche= d with a string that contains an invalid utf-8 character.

- PostgreSQL doesn't allow storing invalid characters in text t= ypes and
=C2=A0 even explicit E'\xD1' anywhere results an= error.
- MySQL Doesn't allow using invalid characters in tex= t types.
=C2=A0
May be it's reasons for thinking ab= out prohibiting invalid characters in text?

WBR, Alexander Turenko.

> sql: LIKE & GLOB pattern comparison issue
>
> Currently function that compares pattern and string for GLOB & LIK= E
> operators doesn't work properly. It uses ICU reading function whic= h
> perhaps was working differently before and the implementation for the<= br> > comparison ending isn't paying attention to some special cases, he= nce
> in those cases it works improperly.
>

It used non-ICU function before, consider commit 198d59ce.


Thank you, I've got it now.
<= div>=C2=A0
> With the patch applied an error will be returned in case there's > invalid UTF-8 symbol in pattern & pattern will not be matched
> with the string that contains it. Some minor corrections to function > were made as well.
>

Nitpicking: 'error will be returned' is third situatuon, other then=
'matched' and 'not matched'.


I guess commit message is about change= s. Do I really need to
mention valid cases that didn't change= ?
=C2=A0
> =D0=A1loses #3251
> =D0=A1loses #3334
> Part of #3572

> > 2. The thing you test is not related to a table and other columns= .
> >=C2=A0 =C2=A0 =C2=A0Please, convert the tests to the next format: = {[[select '' like
> > '_B';]], {1}]]}.
> >=C2=A0 =C2=A0 =C2=A0To make it more readable, you can do it like `= like_testcases` in
> > `sql-tap/collation.test.lua`.
> >
>
> Done.

You are still perform comparisons with table columns.
=
Thank you, fixed it.=C2=A0 More tests now tho.
=C2= =A0

> -/*
> - * For LIKE and GLOB matching on EBCDIC machines, assume that every > - * character is exactly one byte in size.=C2=A0 Also, provde the Utf8= Read()
> - * macro for fast reading of the next character in the common case wh= ere
> - * the next character is ASCII.
> +/**
> + * Providing there are symbols in string s this
> + * macro returns UTF-8 code of character and
> + * promotes pointer to the next symbol in the string.
> + * Otherwise return code is SQL_END_OF_STRING.
>=C2=A0 =C2=A0*/
> -#define Utf8Read(s, e)=C2=A0 =C2=A0 ucnv_getNextUChar(pUtf8conv, &= ;s, e, &status)
> +#define Utf8Read(s, e) (((s) < (e)) ?\
> + ucnv_getNextUChar(pUtf8conv, &(s), (e), &(status)) : 0)

I suggest to add a whitespace before backslash to make it looks better.


Guess so.
=C2=A0
> @@ -643,51 +647,61 @@ static const struct compareInfo likeInfoAlt =3D = { '%',
> '_', 0, 0 };
>=C2=A0 #define SQLITE_MATCH=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A00
>=C2=A0 #define SQLITE_NOMATCH=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01=
>=C2=A0 #define SQLITE_NOWILDCARDMATCH=C2=A0 =C2=A02
> +#define SQL_PROHIBITED_PATTERN=C2=A0 =C2=A03

Update comment before that has a reference to 'patternMatch' functi= on.


Didn't notice it, thnx.
= =C2=A0
>=C2=A0 =C2=A0*
>=C2=A0 =C2=A0* Globbing rules:
>=C2=A0 =C2=A0*
> - *=C2=A0 =C2=A0 =C2=A0 '*'=C2=A0 =C2=A0 =C2=A0 =C2=A0Matches = any sequence of zero or more characters.
> + *=C2=A0 =C2=A0 =C2=A0 '*'=C2=A0 =C2=A0 =C2=A0 Matches any se= quence of zero or more characters.

Why?

>=C2=A0 =C2=A0*
> - *=C2=A0 =C2=A0 =C2=A0 '?'=C2=A0 =C2=A0 =C2=A0 =C2=A0Matches = exactly one character.
> + *=C2=A0 =C2=A0 =C2=A0 '?'=C2=A0 =C2=A0 =C2=A0 Matches exactl= y one character.
>=C2=A0 =C2=A0*
> - *=C2=A0 =C2=A0 =C2=A0[...]=C2=A0 =C2=A0 =C2=A0 Matches one character= from the enclosed list of
> - *=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 characters.=
> + *=C2=A0 =C2=A0 =C2=A0[...]=C2=A0 =C2=A0 =C2=A0Matches one character = from the enclosed list of
> + *=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0characters.<= br> >=C2=A0 =C2=A0*
> - *=C2=A0 =C2=A0 =C2=A0[^...]=C2=A0 =C2=A0 =C2=A0Matches one character= not in the enclosed list.
> + *=C2=A0 =C2=A0 =C2=A0[^...]=C2=A0 =C2=A0 Matches one character not i= n the enclosed list.
>=C2=A0 =C2=A0*

The same question.


It was looking like the indentation fo= r similar table below was different.
I was wrong.
=C2= =A0
> + * @retval SQLITE_MATCH:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Mat= ch.
> + *=C2=A0 =C2=A0 SQLITE_NOMATCH:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 No = match.
> + *=C2=A0 =C2=A0 SQLITE_NOWILDCARDMATCH:=C2=A0 No match in spite of ha= ving *
> + *=C2=A0 =C2=A0 =C2=A0or % wildcards.
> + *=C2=A0 =C2=A0 SQL_PROHIBITED_PATTERN=C2=A0 =C2=A0Pattern contains i= nvalid
> + *=C2=A0 =C2=A0 =C2=A0symbol.

Nitpicking: no colon after SQL_PROHIBITED_PATTERN.
Fixed.
=C2=A0

>=C2=A0 =C2=A0*/
>=C2=A0 static int
> -patternCompare(const char * pattern, /* The glob pattern */
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 const char * string, /* The string to com= pare against the glob */
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 const struct compareInfo *pInfo, /* Infor= mation about how to do
> the compare */
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 UChar32 matchOther /* The escape char (LI= KE) or '[' (GLOB) */
> -=C2=A0 =C2=A0 )
> +sql_utf8_pattern_compare(const char * pattern,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 const char * string,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 const struct compareInfo *pInfo,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 UChar32 matchOther)

Don't get why indent is tabulation + 7 spaces.


Me neither :thinking:. Thank you!
=C2=A0=C2=A0
The function itself looks good except lines longer then 80 columns.

As i said before I don't think breaking th= ese lines will increase readability.
=C2=A0
> @@ -853,8 +903,7 @@ patternCompare(const char * pattern, /* The glob > pattern */
>=C2=A0 int
>=C2=A0 sqlite3_strglob(const char *zGlobPattern, const char *zString) >=C2=A0 {
> - return patternCompare(zGlobPattern, zString, &globInfo,
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0'[');
> + return sql_utf8_pattern_compare(zGlobPattern, zString, &globInfo= , '[');
>=C2=A0 }
>
>=C2=A0 /*
> @@ -864,7 +913,7 @@ sqlite3_strglob(const char *zGlobPattern, const ch= ar
> *zString)
>=C2=A0 int
>=C2=A0 sqlite3_strlike(const char *zPattern, const char *zStr, unsigned= int esc)
>=C2=A0 {
> - return patternCompare(zPattern, zStr, &likeInfoNorm, esc);
> + return sql_utf8_pattern_compare(zPattern, zStr, &likeInfoNorm, e= sc);
>=C2=A0 }
>

Should we add sqlite3_result_error is case of SQL_PROHIBITED_PATTERN for these two functions?


It doesn't matter for "sqlite= 3_strglob()", since it's only used internally
with valid= explicitly given pattern in "analysis_loader()".
Same = for "sqlite3_strlike()".

=C2=A0
> -=C2=A0 =C2=A0 {"LIKE", "like"},
> -=C2=A0 =C2=A0 {"GLOB", "glob"},
> +-- NOTE: This test needs refactoring after deletion of GLOB &
> +-- type restrictions for LIKE.
> +--=C2=A0 =C2=A0 {"LIKE", "like"},
> +--=C2=A0 =C2=A0 {"GLOB", "glob"},

I would add related issue number.


True. Done.
=C2=A0
>=C2=A0 =C2=A0 =C2=A0 {"AND", "and"},
>=C2=A0 =C2=A0 =C2=A0 {"OR", "or"},
>=C2=A0 =C2=A0 =C2=A0 {"MATCH", "match"},
> @@ -96,7 +98,7 @@ operations =3D {
>=C2=A0 =C2=A0 =C2=A0 {"+", "-"},
>=C2=A0 =C2=A0 =C2=A0 {"<<", ">>", "= ;&", "|"},
>=C2=A0 =C2=A0 =C2=A0 {"<", "<=3D", ">= ", ">=3D"},
> -=C2=A0 =C2=A0 {"=3D", "=3D=3D", "!=3D",= "<>", "LIKE", "GLOB"}, --"MATCH&q= uot;, "REGEXP"},
> +=C2=A0 =C2=A0 {"=3D", "=3D=3D", "!=3D",= "<>"}, --"LIKE", "GLOB"}, "MATCH&= quot;, "REGEXP"},

Leave comment before MATCH and REGEXP to avoid surprises after removing
your comment.


Done.
=C2=A0
> --- /dev/null
> +++ b/test/sql-tap/gh-3251-string-pattern-comparison.lua
> @@ -0,0 +1,238 @@
> +#!/usr/bin/env tarantool
> +test =3D require("sqltester")
> +test:plan(106)
> +
> +local prefix =3D "like-test-"
> +
> +-- Unciode byte sequences.
> +

1. Typo: unicode.
2. Extra empty line.


Fixed.
=C2=A0
> +-- Invalid testcases.
> +
> +for i, tested_string in ipairs(invalid_testcases) do
> + local test_name =3D prefix .. "2." .. tostring(i)
> + local test_itself =3D "SELECT 'abc' LIKE 'ab" = .. tested_string .. "';"
> +
> +-- We should raise an error if pattern contains invalid characters. > +

1. Trailing whitespaces.
2. Why comments are not indented as the other code?
3. test_name and test_itself before the section comment looks strange
=C2=A0 =C2=A0for me here.

diff --git a/= src/box/sql/func.c b/src/box/sql/func.c
index 5b53076..2f989d4 10= 0644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c<= /div>
@@ -623,7 +623,7 @@ struct compareInfo {
=C2=A0 * promo= tes pointer to the next symbol in the string.
=C2=A0 * Otherwise = return code is SQL_END_OF_STRING.
=C2=A0 */
-#define Ut= f8Read(s, e) (((s) < (e)) ?\
+#define Utf8Read(s, e) (((s) <= ; (e)) ? \
=C2=A0 ucnv_get= NextUChar(pUtf8conv, &(s), (e), &(status)) : 0)
=C2=A0
=C2=A0#define SQL_END_OF_STRING=C2=A0 =C2=A0 =C2=A0 =C2=A0 0
<= div>@@ -642,7 +642,7 @@ static const struct compareInfo likeInfoNorm =3D { = '%', '_', 0, 1 };
=C2=A0static const struct compa= reInfo likeInfoAlt =3D { '%', '_', 0, 0 };
=C2=A0=
=C2=A0/*
- * Possible error returns from patternMatch(= )
+ * Possible error returns from sql_utf8_pattern_compare()
=C2=A0 */
=C2=A0#define SQLITE_MATCH=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A00
=C2=A0#define SQLITE_NOMATCH=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01
@@ -655,14 +655,14 @@ static = const struct compareInfo likeInfoAlt =3D { '%', '_', 0, 0 }= ;
=C2=A0 *
=C2=A0 * Globbing rules:
=C2=A0 *<= /div>
- *=C2=A0 =C2=A0 =C2=A0 '*'=C2=A0 =C2=A0 =C2=A0 Matches a= ny sequence of zero or more characters.
+ *=C2=A0 =C2=A0 =C2=A0 &= #39;*'=C2=A0 =C2=A0 =C2=A0 =C2=A0Matches any sequence of zero or more c= haracters.
=C2=A0 *
- *=C2=A0 =C2=A0 =C2=A0 '?'= =C2=A0 =C2=A0 =C2=A0 Matches exactly one character.
+ *=C2=A0 =C2= =A0 =C2=A0 '?'=C2=A0 =C2=A0 =C2=A0 =C2=A0Matches exactly one charac= ter.
=C2=A0 *
- *=C2=A0 =C2=A0 =C2=A0[...]=C2=A0 =C2=A0= =C2=A0Matches one character from the enclosed list of
- *=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0characters.
+ *= =C2=A0 =C2=A0 =C2=A0[...]=C2=A0 =C2=A0 =C2=A0 Matches one character from th= e enclosed list of
+ *=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 characters.
=C2=A0 *
- *=C2=A0 =C2=A0 =C2= =A0[^...]=C2=A0 =C2=A0 Matches one character not in the enclosed list.
+ *=C2=A0 =C2=A0 =C2=A0[^...]=C2=A0 =C2=A0 =C2=A0Matches one characte= r not in the enclosed list.
=C2=A0 *
=C2=A0 * With the = [...] and [^...] matching, a ']' character can be
=C2=A0 = * included in the list by making it the first character after
@@ = -694,14 +694,14 @@ static const struct compareInfo likeInfoAlt =3D { '%= ', '_', 0, 0 };
=C2=A0 * =C2=A0 =C2=A0SQLITE_NOMATCH:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 N= o match.
=C2=A0 * =C2=A0 = =C2=A0SQLITE_NOWILDCARDMATCH:=C2=A0 No match in spite of having *
=C2=A0 * =C2=A0 =C2=A0 or % wild= cards.
- * =C2=A0 =C2=A0SQ= L_PROHIBITED_PATTERN=C2=A0 =C2=A0Pattern contains invalid
+ * =C2=A0 =C2=A0SQL_PROHIBITED_PATTERN:=C2= =A0 Pattern contains invalid
=C2=A0 * =C2=A0 =C2=A0 symbol.
=C2=A0 */
=C2=A0st= atic int
=C2=A0sql_utf8_pattern_compare(const char * pattern,
- =C2=A0 =C2=A0 =C2=A0 =C2=A0= const char * string,
- =C2= =A0 =C2=A0 =C2=A0 =C2=A0const struct compareInfo *pInfo,
- =C2=A0 =C2=A0 =C2=A0 =C2=A0UChar32 matchOt= her)
+ const char * str= ing,
+ const struct com= pareInfo *pInfo,
+ UCha= r32 matchOther)
=C2=A0{
=C2=A0 UChar32 c, c2; /* Ne= xt pattern and input string chars */
=C2=A0 UChar32 matchOne =3D pInfo->matchOne; /* "?" or "_" */
diff = --git a/test/sql-tap/e_expr.test.lua b/test/sql-tap/e_expr.test.lua
index 051210a..9780d2c 100755
--- a/test/sql-tap/e_expr.test.l= ua
+++ b/test/sql-tap/e_expr.test.lua
@@ -78,7 +78,7 @@= local operations =3D {
=C2=A0 =C2=A0 =C2=A0{"!=3D", &q= uot;ne2"},
=C2=A0 =C2=A0 =C2=A0{"IS", "is&quo= t;},
=C2=A0-- NOTE: This test needs refactoring after deletion of= GLOB &
--- type rest= rictions for LIKE.
+-- ty= pe restrictions for LIKE. (See #3572)
=C2=A0--=C2=A0 =C2=A0 {&quo= t;LIKE", "like"},
=C2=A0--=C2=A0 =C2=A0 {"GLO= B", "glob"},
=C2=A0 =C2=A0 =C2=A0{"AND",= "and"},
@@ -98,7 +98,12 @@ operations =3D {
= =C2=A0 =C2=A0 =C2=A0{"+", "-"},
=C2=A0 =C2=A0= =C2=A0{"<<", ">>", "&", &quo= t;|"},
=C2=A0 =C2=A0 =C2=A0{"<", "<=3D&= quot;, ">", ">=3D"},
-=C2=A0 =C2=A0 {&q= uot;=3D", "=3D=3D", "!=3D", "<>"},= --"LIKE", "GLOB"}, "MATCH", "REGEXP&quo= t;},
+-- NOTE: This test needs refactoring after deletion of GLOB= &
+-- type restricti= ons for LIKE. (See #3572)
+-- Another NOTE: MATCH & REGEXP ar= en't supported in Tarantool &
+-- are waiting for their hour, don't confuse them
=
+-- being commented with tick= et above.
+=C2=A0 =C2=A0 {"=3D", "=3D=3D", &q= uot;!=3D", "<>"}, --"LIKE", "GLOB"= }, --"MATCH", "REGEXP"},
=C2=A0 =C2=A0 =C2=A0= {"AND"},
=C2=A0 =C2=A0 =C2=A0{"OR"},
=C2=A0}
diff --git a/test/sql-tap/gh-3251-string-pattern-compar= ison.test.lua b/test/sql-tap/gh-3251-string-pattern-comparison.test.lua
new file mode 100755
index 0000000..a823bc6
--- = /dev/null
+++ b/test/sql-tap/gh-3251-string-pattern-comparison.te= st.lua
@@ -0,0 +1,281 @@
+#!/usr/bin/env tarantool
+test =3D require("sqltester")
+test:plan(128)
+
+local prefix =3D "like-test-"
+
+-- Unicode byte sequences.
+local valid_testcases =3D {<= /div>
+ '\x01',
+ '\x09',
+ '\x1F',
+ '\x7F',
+ '\xC2\x80',
+ '\xC2\x90',
+ '\xC2\x9F',
+ '\xE2\x80\xA8',
+ '\x20\x0B',
+ '\xE2\x80\xA9',
+}
+
+-- Non-Uni= code byte sequences.
+local invalid_testcases =3D {
+ '\xE2\x80',
+ '\xFE\xFF',
+ '\xC2',
+ '\xED\xB0\x80',
+ '\xD0',
+}
+
+l= ocal like_test_cases =3D
+{
+ {"1.1",
+= "SELECT 'AB' LIKE '_B';",
+ {0, {1}} },
+ {"1.2",
+ "SELECT 'CD' LIKE '_B';",
+ {0, {0}} },
+ {"1.3",
+ "SELECT '' LIKE '_B';= ",
+ {0, {0}} },
+ {"1.4",
+ "SELECT 'AB' LIKE &= #39;%B';",
+ {0,= {1}} },
+ {"1.5"= ;,
+ "SELECT 'CD= ' LIKE '%B';",
+= {0, {0}} },
+ {&q= uot;1.6",
+ "SE= LECT '' LIKE '%B';",
+ {0, {0}} },
+ = {"1.7",
+ "SELECT 'AB' LIKE 'A__';",
+ {0, {0}} },
+ {"1.8",
+ "SELECT 'CD' LIKE 'A__';",
+ {0, {0}} },
+ {"1.9",
+ "SELECT '' LIKE 'A__';&q= uot;,
+ {0, {0}} },
=
+ {"1.10",
= + "SELECT 'AB' LIKE &#= 39;A_';",
+ {0, = {1}} },
+ {"1.11"= ;,
+ "SELECT 'CD= ' LIKE 'A_';",
+= {0, {0}} },
+ {&q= uot;1.12",
+ "S= ELECT '' LIKE 'A_';",
+ {0, {0}} },
+= {"1.13",
+ "SELECT 'AB' LIKE 'A';",
+ {0, {0}} },
+ {"1.14",
+ "SELECT 'CD' LIKE 'A';",
+ {0, {0}} },
+ {"1.15",
+ "SELECT '' LIKE 'A';&quo= t;,
+ {0, {0}} },
+ {"1.16",
+<= span style=3D"white-space:pre"> "SELECT 'AB' LIKE '= ;_';",
+ {0, {0}= } },
+ {"1.17",<= /div>
+ "SELECT 'CD= 9; LIKE '_';",
+ {0, {0}} },
+ {"= 1.18",
+ "SELEC= T '' LIKE '_';",
+ {0, {0}} },
+ {"1.19",
+ &= quot;SELECT 'AB' LIKE '__';",
+ {0, {1}} },
+ {"1.20",
+ "SELECT 'CD' LIKE '__';",
+ {0, {1}} },
+ {"1.21",
+ "SELECT '' LIKE '__';"= ,
+ {0, {0}} },
+ {"1.22",
+ "SELECT 'AB' LIKE '%= A';",
+ {0, {0}}= },
+ {"1.23",
+ "SELECT 'AB'= ; LIKE '%C';",
+ {0, {0}} },
+ {"= 1.24",
+ "SELEC= T 'ab' LIKE '%df';",
+ {0, {0}} },
+ = {"1.25",
+ "SELECT 'abCDF' LIKE '%df';",
+ {0, {1}} },
+ {"1.26",
+ "SELECT 'CDF' LIKE '%df';",
+ {0, {1}} },
+<= span style=3D"white-space:pre"> {"1.27",
+ "SELECT 'ab' LIKE 'a_&#= 39;;",
+ {0, {1}} },=
+ {"1.28",
+ "SELECT 'abCDF'= ; LIKE 'a_';",
+ {0, {0}} },
+ {"= 1.29",
+ "SELEC= T 'CDF' LIKE 'a_';",
+ {0, {0}} },
+ = {"1.30",
+ "SELECT 'ab' LIKE 'ab%';",
+ {0, {1}} },
+ {"1.31",
+ "SELECT 'abCDF' LIKE 'ab%';",
+ {0, {1}} },
+ {"1.32",
+ "SELECT 'CDF' LIKE 'ab%&= #39;;",
+ {0, {0}} }= ,
+ {"1.33",
+ "SELECT 'ab' = LIKE 'abC%';",
+ {0, {0}} },
+ {"= 1.34",
+ "SELEC= T 'abCDF' LIKE 'abC%';",
+ {0, {1}} },
+ {"1.35",
+ = "SELECT 'CDF' LIKE 'abC%';",
+ {0, {0}} },
+ {"1.36",
+ "SELECT 'ab' LIKE 'a_%';"= ;,
+ {0, {1}} },
+ {"1.37",
+ "SELECT 'abCDF' LIKE &#= 39;a_%';",
+ {0,= {1}} },
+ {"1.38&quo= t;,
+ "SELECT 'C= DF' LIKE 'a_%';",
+ {0, {0}} },
+}
+
+test:do_catchsq= l_set_test(like_test_cases, prefix)
+
+-- Invalid testc= ases.
+
+for i, tested_string in ipairs(invalid_testcas= es) do
+
+-- We should raise an error if pattern contai= ns invalid characters.
+ l= ocal test_name =3D prefix .. "2." .. tostring(i)
+ local test_itself =3D "SELECT '= abc' LIKE 'ab" .. tested_string .. "';"
+ test:do_catchsql_test(
+ test_name,
+ test_itself, {
+ -- <test_name>
+ 1, "LIKE or GLOB pattern can only contain UTF-8= characters"
+ -- &= lt;test_name>
+ })
+
+ test_name =3D p= refix .. "3." .. tostring(i)
+ test_itself =3D "SELECT 'abc' LIKE 'abc&quo= t; .. tested_string .. "';"
+ test:do_catchsql_test(
+ test_name,
+ <= /span>test_itself, {
+ -= - <test_name>
+ 1,= "LIKE or GLOB pattern can only contain UTF-8 characters"
+ -- <test_name>
+ })
+
+ test_name =3D prefix .. "4." .= . tostring(i)
+ test_itsel= f =3D "SELECT 'abc' LIKE 'ab" .. tested_string .. &qu= ot;c';"
+ test:do= _catchsql_test(
+ test_na= me,
+ test_itself, {
+ -- <test_name>
=
+ 1, "LIKE or GLOB patte= rn can only contain UTF-8 characters"
+ -- <test_name>
+ })
+
+-- Just skipping if row value = predicand contains invalid character.
+
+ test_name =3D prefix .. "5." .. tostrin= g(i)
+ test_itself =3D &qu= ot;SELECT 'ab" .. tested_string .. "' LIKE 'abc';= "
+ test:do_execsql_t= est(
+ test_name,
+ test_itself, {
+ -- <test_name>
+ 0
+ -- <test_name>
+ })
+
+ <= /span>test_name =3D prefix .. "6." .. tostring(i)
+ test_itself =3D "SELECT 'abc&q= uot; .. tested_string .. "' LIKE 'abc';"
+<= span style=3D"white-space:pre"> test:do_execsql_test(
+ test_name,
+ test_itself, {
+ -- <test_name>
+ 0
+ --= <test_name>
+ })
+
+ test_name =3D= prefix .. "7." .. tostring(i)
+ test_itself =3D "SELECT 'ab" .. tested_strin= g .. "c' LIKE 'abc';"
+ test:do_execsql_test(
+ test_name,
+ = test_itself, {
+ = -- <test_name>
+ 0=
+ -- <test_name><= /div>
+ })
+end
=
+
+-- Valid testcases.
+
+for i, tested_= string in ipairs(valid_testcases) do
+ test_name =3D prefix .. "8." .. tostring(i)
+ local test_itself =3D "SELE= CT 'abc' LIKE 'ab" .. tested_string .. "';"<= /div>
+ test:do_execsql_test(
+ test_name,
+ test_itself, {
+ -- <test_name>
+ 0
+ -- <test_name>
+= })
+
+ = test_name =3D prefix .. "9." .. tostring(i)
+ test_itself =3D "SELECT 'abc' LI= KE 'abc" .. tested_string .. "';"
+ test:do_execsql_test(
+ test_name,
+ test_itself, {
+ -- <test_name>
+ 0
+ -- <t= est_name>
+ })
+
+ test_name =3D prefi= x .. "10." .. tostring(i)
+ test_itself =3D "SELECT 'abc' LIKE 'ab" .= . tested_string .. "c';"
+ test:do_execsql_test(
+ test_name,
+ test_itself, {
+ -- &l= t;test_name>
+ 0
+ -- <test_name>
=
+ })
+ test_name =3D prefix .. "11." .. tostri= ng(i)
+ test_itself =3D &q= uot;SELECT 'ab" .. tested_string .. "' LIKE 'abc'= ;"
+ test:do_execsql_= test(
+ test_name,
<= div>+ test_itself, {
+ -- <test_name>
+ 0
+ -- <test_name>
+ })
+
+ = test_name =3D prefix .. "12." .. tostring(i)
+ test_itself =3D "SELECT 'abc= " .. tested_string .. "' LIKE 'abc';"
= + test:do_execsql_test(
+<= span style=3D"white-space:pre"> test_name,
+ test_itself, {
+ -- <test_name>
+ 0
+ -- <test_name>
+ = })
+
+ test_name= =3D prefix .. "13." .. tostring(i)
+ test_itself =3D "SELECT 'ab" .. tested_= string .. "c' LIKE 'abc';"
+ test:do_execsql_test(
+ test_name,
+ test_itself, {
+ -- <test_name>
+ 0
+ -- <test_name= >
+ })
+end<= /div>
+
+test:finish_test()
=C2=A0
--0000000000001245790571ffe284--