From 40f9d33a9351351a6436ed59a45603c08484177f Mon Sep 17 00:00:00 2001 From: maxpozdeev Date: Thu, 10 Nov 2022 21:24:41 +0300 Subject: [PATCH] + now search is case-insensitive for non-latin characters in sqlite database --- src/includes/api/TagsController.php | 6 ++++-- src/includes/api/TasksController.php | 8 ++++++-- src/includes/class.db.mysql.php | 6 ++++++ src/includes/class.db.mysqli.php | 6 ++++++ src/includes/class.db.sqlite3.php | 22 ++++++++++++++++++++++ src/includes/class.dbconnection.php | 1 + 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/includes/api/TagsController.php b/src/includes/api/TagsController.php index f9ef09a..814c1ae 100644 --- a/src/includes/api/TagsController.php +++ b/src/includes/api/TagsController.php @@ -72,8 +72,10 @@ class TagsController extends ApiController { $db = DBConnection::instance(); $begin = trim(_get('q')); $limit = 8; - $q = $db->dq("SELECT name,id FROM {$db->prefix}tags INNER JOIN {$db->prefix}tag2task ON id=tag_id WHERE list_id=$listId AND name LIKE ". - $db->quoteForLike('%s%%',$begin) ." GROUP BY tag_id ORDER BY name LIMIT $limit"); + $q = $db->dq("SELECT name,id FROM {$db->prefix}tags + INNER JOIN {$db->prefix}tag2task ON id=tag_id + WHERE list_id=$listId AND ". $db->like('name', '%s%%', $begin). " + GROUP BY tag_id ORDER BY name LIMIT $limit"); $t = array(); while ($r = $q->fetchRow()) { $t[] = $r[0]; diff --git a/src/includes/api/TasksController.php b/src/includes/api/TasksController.php index 808e02f..0f04bfe 100644 --- a/src/includes/api/TasksController.php +++ b/src/includes/api/TasksController.php @@ -73,8 +73,12 @@ class TasksController extends ApiController { $s = trim(_get('s')); if ($s != '') { - if (preg_match("|^#(\d+)$|", $s, $m)) $sqlWhere .= " AND {$db->prefix}todolist.id = ". (int)$m[1]; - else $sqlWhere .= " AND (title LIKE ". $db->quoteForLike("%%%s%%",$s). " OR note LIKE ". $db->quoteForLike("%%%s%%",$s). ")"; + if (preg_match("|^#(\d+)$|", $s, $m)) { + $sqlWhere .= " AND {$db->prefix}todolist.id = ". (int)$m[1]; + } + else { + $sqlWhere .= " AND (". $db->like("title", "%%%s%%", $s). " OR ". $db->like("note", "%%%s%%", $s). ")"; + } } $sort = (int)_get('sort'); diff --git a/src/includes/class.db.mysql.php b/src/includes/class.db.mysql.php index 30dcb37..48a6aee 100644 --- a/src/includes/class.db.mysql.php +++ b/src/includes/class.db.mysql.php @@ -170,6 +170,12 @@ class Database_Mysql extends Database_Abstract return '\''. sprintf($format, $string). '\''; } + function like(string $column, string $format, string $string): string + { + $column = str_replace('`', '``', $column); + return '`'. $column. '` LIKE '. $this->quoteForLike($format, $string); + } + function lastInsertId(?string $name = null): ?string { $ret = $this->dbh->lastInsertId(); diff --git a/src/includes/class.db.mysqli.php b/src/includes/class.db.mysqli.php index a289468..0565543 100644 --- a/src/includes/class.db.mysqli.php +++ b/src/includes/class.db.mysqli.php @@ -143,6 +143,12 @@ class Database_Mysqli extends Database_Abstract return '\''. sprintf($format, $string). '\''; } + function like(string $column, string $format, string $string): string + { + $column = str_replace('`', '``', $column); + return '`'. $column. '` LIKE '. $this->quoteForLike($format, $string); + } + function tableExists(string $table): bool { $r = $this->sq("SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", diff --git a/src/includes/class.db.sqlite3.php b/src/includes/class.db.sqlite3.php index c3560d1..0abeaf2 100644 --- a/src/includes/class.db.sqlite3.php +++ b/src/includes/class.db.sqlite3.php @@ -73,6 +73,8 @@ class Database_Sqlite3 extends Database_Abstract PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ); $this->dbh = new PDO("sqlite:$filename", null, null, $options); //throws PDOException + $this->dbh->sqliteCreateFunction('utf8_lower', [$this, 'utf8lower'], 1); + $this->dbh->sqliteCreateCollation('UTF8CI', [$this, 'utf8ci']); } /* @@ -162,6 +164,15 @@ class Database_Sqlite3 extends Database_Abstract return $this->dbh->quote(sprintf($format, $string)). " ESCAPE '\'"; } + /** + * Produce case-insensitive like + */ + function like(string $column, string $format, string $string): string + { + $column = str_replace('"', '""', $column); + return 'utf8_lower("'. $column. '") LIKE '. $this->quoteForLike($format, mb_strtolower($string, 'UTF-8')); + } + function lastInsertId(?string $name = null): ?string { $ret = $this->dbh->lastInsertId(); @@ -192,4 +203,15 @@ class Database_Sqlite3 extends Database_Abstract } return false; } + + public function utf8lower($value) + { + if (is_null($value)) return ''; + return mb_strtolower((string)$value, 'UTF-8'); + } + + public function utf8ci(string $str1, string $str2): int + { + return strcmp(mb_strtolower($str1, 'UTF-8'), mb_strtolower($str2, 'UTF-8')); + } } diff --git a/src/includes/class.dbconnection.php b/src/includes/class.dbconnection.php index 1a16460..606a79e 100644 --- a/src/includes/class.dbconnection.php +++ b/src/includes/class.dbconnection.php @@ -49,6 +49,7 @@ abstract class Database_Abstract abstract function affected(): int; abstract function quote($value): string; abstract function quoteForLike(string $format, string $string): string; + abstract function like(string $column, string $format, string $string): string; abstract function lastInsertId(?string $name = null): ?string; abstract function tableExists(string $table): bool; abstract function tableFieldExists(string $table, string $field): bool;