fix wildcard search for mysql

This commit is contained in:
Shish 2016-06-07 02:05:19 +01:00
parent e0d3d53479
commit d185fd354b

View File

@ -316,7 +316,13 @@ class Image {
} }
if(count($tags) === 0) { if(count($tags) === 0) {
$row = $database->get_row('SELECT images.* FROM images WHERE images.id '.$gtlt.' '.$this->id.' ORDER BY images.id '.$dir.' LIMIT 1'); $row = $database->get_row('
SELECT images.*
FROM images
WHERE images.id '.$gtlt.' '.$this->id.'
ORDER BY images.id '.$dir.'
LIMIT 1
');
} }
else { else {
$tags[] = 'id'. $gtlt . $this->id; $tags[] = 'id'. $gtlt . $this->id;
@ -355,7 +361,11 @@ class Image {
public function set_owner(User $owner) { public function set_owner(User $owner) {
global $database; global $database;
if($owner->id != $this->owner_id) { if($owner->id != $this->owner_id) {
$database->execute("UPDATE images SET owner_id=:owner_id WHERE id=:id", array("owner_id"=>$owner->id, "id"=>$this->id)); $database->execute("
UPDATE images
SET owner_id=:owner_id
WHERE id=:id
", array("owner_id"=>$owner->id, "id"=>$this->id));
log_info("core_image", "Owner for Image #{$this->id} set to {$owner->name}", false, array("image_id" => $this->id)); log_info("core_image", "Owner for Image #{$this->id} set to {$owner->name}", false, array("image_id" => $this->id));
} }
} }
@ -368,7 +378,13 @@ class Image {
public function get_tag_array() { public function get_tag_array() {
global $database; global $database;
if(!isset($this->tag_array)) { if(!isset($this->tag_array)) {
$this->tag_array = $database->get_col("SELECT tag FROM image_tags JOIN tags ON image_tags.tag_id = tags.id WHERE image_id=:id ORDER BY tag", array("id"=>$this->id)); $this->tag_array = $database->get_col("
SELECT tag
FROM image_tags
JOIN tags ON image_tags.tag_id = tags.id
WHERE image_id=:id
ORDER BY tag
", array("id"=>$this->id));
} }
return $this->tag_array; return $this->tag_array;
} }
@ -570,10 +586,18 @@ class Image {
$database->execute(" $database->execute("
UPDATE tags UPDATE tags
SET count = count - 1 SET count = count - 1
WHERE id IN (SELECT tag_id FROM image_tags WHERE image_id = :id)", array("id"=>$this->id) WHERE id IN (
); SELECT tag_id
FROM image_tags
WHERE image_id = :id
)
", array("id"=>$this->id));
} }
$database->execute("DELETE FROM image_tags WHERE image_id=:id", array("id"=>$this->id)); $database->execute("
DELETE
FROM image_tags
WHERE image_id=:id
", array("id"=>$this->id));
} }
/** /**
@ -601,10 +625,13 @@ class Image {
} }
$id = $database->get_one( $id = $database->get_one(
$database->scoreql_to_sql( $database->scoreql_to_sql("
"SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)" SELECT id
), FROM tags
array("tag"=>$tag)); WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
"),
array("tag"=>$tag)
);
if(empty($id)) { if(empty($id)) {
// a new tag // a new tag
$database->execute( $database->execute(
@ -617,15 +644,19 @@ class Image {
} }
else { else {
// user of an existing tag // user of an existing tag
$database->execute( $database->execute("
"INSERT INTO image_tags(image_id, tag_id) VALUES(:iid, :tid)", INSERT INTO image_tags(image_id, tag_id)
array("iid"=>$this->id, "tid"=>$id)); VALUES(:iid, :tid)
", array("iid"=>$this->id, "tid"=>$id));
} }
$database->execute( $database->execute(
$database->scoreql_to_sql( $database->scoreql_to_sql("
"UPDATE tags SET count = count + 1 WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)" UPDATE tags
), SET count = count + 1
array("tag"=>$tag)); WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
"),
array("tag"=>$tag)
);
} }
log_info("core_image", "Tags for Image #{$this->id} set to: ".implode(" ", $tags), null, array("image_id" => $this->id)); log_info("core_image", "Tags for Image #{$this->id} set to: ".implode(" ", $tags), null, array("image_id" => $this->id));
@ -789,15 +820,10 @@ class Image {
$img_querylets[] = new ImgQuerylet($querylet, $positive); $img_querylets[] = new ImgQuerylet($querylet, $positive);
} }
} else { } else {
$expansions = Tag::resolve_wildcard($term); $tag_querylets[] = new TagQuerylet(Tag::sanitise_wildcard($term), $positive);
if ($expansions) {
if ($positive) $positive_tag_count++; if ($positive) $positive_tag_count++;
else $negative_tag_count++; else $negative_tag_count++;
} }
foreach ($expansions as $expanded_term) {
$tag_querylets[] = new TagQuerylet($expanded_term, $positive);
}
}
} }
/* /*
@ -823,6 +849,7 @@ class Image {
// one positive tag (a common case), do an optimised search // one positive tag (a common case), do an optimised search
else if($positive_tag_count === 1 && $negative_tag_count === 0) { else if($positive_tag_count === 1 && $negative_tag_count === 0) {
# "LIKE" to account for wildcards
$query = new Querylet($database->scoreql_to_sql(" $query = new Querylet($database->scoreql_to_sql("
SELECT * SELECT *
FROM ( FROM (
@ -840,15 +867,9 @@ class Image {
// more than one positive tag, or more than zero negative tags // more than one positive tag, or more than zero negative tags
else { else {
if($database->get_driver_name() === "mysql") if($database->get_driver_name() === "mysql")
$query = Image::build_ugly_search_querylet( $query = Image::build_ugly_search_querylet($tag_querylets);
$tag_querylets,
$positive_tag_count
);
else else
$query = Image::build_accurate_search_querylet( $query = Image::build_accurate_search_querylet($tag_querylets);
$tag_querylets,
$positive_tag_count
);
} }
/* /*
@ -893,14 +914,10 @@ class Image {
* All the subqueries are executed every time for every row in the * All the subqueries are executed every time for every row in the
* images table. Yes, MySQL does suck this much. * images table. Yes, MySQL does suck this much.
* *
* @param array $tag_querylets * @param TagQuerylet[] $tag_querylets
* @param int $positive_tag_count
* @return Querylet * @return Querylet
*/ */
private static function build_accurate_search_querylet( private static function build_accurate_search_querylet($tag_querylets) {
$tag_querylets,
$positive_tag_count
) {
global $database; global $database;
$positive_tag_id_array = array(); $positive_tag_id_array = array();
@ -911,7 +928,7 @@ class Image {
$database->scoreql_to_sql(" $database->scoreql_to_sql("
SELECT id SELECT id
FROM tags FROM tags
WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag) WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag)
"), "),
array("tag" => $tq->tag) array("tag" => $tq->tag)
); );
@ -931,52 +948,45 @@ class Image {
} }
} }
$have_pos = count($positive_tag_id_array) > 0; assert('$positive_tag_id_array || $negative_tag_id_array');
$have_neg = count($negative_tag_id_array) > 0; $wheres = array();
if ($positive_tag_id_array) {
$sql = "";
if ($have_pos) {
$positive_tag_id_list = join(', ', $positive_tag_id_array); $positive_tag_id_list = join(', ', $positive_tag_id_array);
$sql .= " $wheres[] = "tag_id IN ($positive_tag_id_list)";
SELECT image_id
FROM image_tags
WHERE tag_id IN ($positive_tag_id_list)
GROUP BY image_id
HAVING COUNT(image_id)>=$positive_tag_count
";
} }
if ($have_pos && $have_neg) { if ($negative_tag_id_array) {
$sql .= " EXCEPT ";
}
if ($have_neg) {
$negative_tag_id_list = join(', ', $negative_tag_id_array); $negative_tag_id_list = join(', ', $negative_tag_id_array);
$sql .= " $wheres[] = "tag_id NOT IN ($negative_tag_id_list)";
SELECT image_id
FROM image_tags
WHERE tag_id IN ($negative_tag_id_list)
";
} }
$wheres_str = join(" AND ", $wheres);
return new Querylet(" return new Querylet("
SELECT images.* SELECT images.*
FROM images FROM images
WHERE images.id IN ($sql) WHERE images.id IN (
"); SELECT image_id
FROM image_tags
WHERE $wheres_str
GROUP BY image_id
HAVING COUNT(image_id) >= :search_score
)
", array("search_score"=>count($positive_tag_id_array)));
} }
/** /**
* this function exists because mysql is a turd, see the docs for * this function exists because mysql is a turd, see the docs for
* build_accurate_search_querylet() for a full explanation * build_accurate_search_querylet() for a full explanation
* *
* @param array $tag_querylets * @param TagQuerylet[] $tag_querylets
* @param int $positive_tag_count
* @return Querylet * @return Querylet
*/ */
private static function build_ugly_search_querylet( private static function build_ugly_search_querylet($tag_querylets) {
$tag_querylets,
$positive_tag_count
) {
global $database; global $database;
$positive_tag_count = 0;
foreach($tag_querylets as $tq) {
if($tq->positive) $positive_tag_count++;
}
// only negative tags - shortcut to fail // only negative tags - shortcut to fail
if($positive_tag_count == 0) { if($positive_tag_count == 0) {
// TODO: This isn't currently implemented. // TODO: This isn't currently implemented.
@ -993,7 +1003,7 @@ class Image {
$terms = array(); $terms = array();
foreach($tag_querylets as $tq) { foreach($tag_querylets as $tq) {
$sign = $tq->positive ? "+" : "-"; $sign = $tq->positive ? "+" : "-";
$sql .= ' '.$sign.' IF(tag LIKE :tag'.Image::$tag_n.', 1, 0)'; $sql .= ' '.$sign.' IF(SUM(tag LIKE :tag'.Image::$tag_n.'), 1, 0)';
$terms['tag'.Image::$tag_n] = $tq->tag; $terms['tag'.Image::$tag_n] = $tq->tag;
Image::$tag_n++; Image::$tag_n++;
} }
@ -1001,19 +1011,18 @@ class Image {
$tag_id_array = array(); $tag_id_array = array();
$x = 0; foreach($tag_querylets as $tq) {
foreach($tag_search->variables as $tag) {
$tag_ids = $database->get_col( $tag_ids = $database->get_col(
$database->scoreql_to_sql(" $database->scoreql_to_sql("
SELECT id SELECT id
FROM tags FROM tags
WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag) WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(:tag)
"), "),
array("tag" => $tag) array("tag" => $tq->tag)
); );
$tag_id_array = array_merge($tag_id_array, $tag_ids); $tag_id_array = array_merge($tag_id_array, $tag_ids);
if($tag_querylets[$x]->positive && count($tag_ids) == 0) { if($tq->positive && count($tag_ids) == 0) {
# one of the positive tags had zero results, therefor there # one of the positive tags had zero results, therefor there
# can be no results; "where 1=0" should shortcut things # can be no results; "where 1=0" should shortcut things
return new Querylet(" return new Querylet("
@ -1022,15 +1031,13 @@ class Image {
WHERE 1=0 WHERE 1=0
"); ");
} }
$x++;
} }
Image::$tag_n = 0; Image::$tag_n = 0;
return new Querylet(' return new Querylet('
SELECT * SELECT *
FROM ( FROM (
SELECT images.*, SUM('.$tag_search->sql.') AS score SELECT images.*, ('.$tag_search->sql.') AS score
FROM images FROM images
LEFT JOIN image_tags ON image_tags.image_id = images.id LEFT JOIN image_tags ON image_tags.image_id = images.id
JOIN tags ON image_tags.tag_id = tags.id JOIN tags ON image_tags.tag_id = tags.id
@ -1154,34 +1161,24 @@ class Tag {
/** /**
* @param string $tag * @param string $tag
* @return array * @return string
*/ */
public static function resolve_wildcard($tag) { public static function sanitise_wildcard($tag) {
// if there is no wildcard, return the tag // if there is no wildcard, return the tag
if(strpos($tag, "*") === false) { if(strpos($tag, "*") === false) {
return array($tag); return $tag;
} }
// if the whole match is wild, return null to save the database // if the whole match is wild, save the database
else if(str_replace("*", "", $tag) == "") { else if(str_replace("*", "", $tag) == "") {
return array(); return "invalid-wildcard";
} }
// else find some matches // else translate to sql
else { else {
global $database; $tag = str_replace("%", "\%", $tag);
$db_wild_tag = str_replace("%", "\%", $tag); $tag = str_replace("*", "%", $tag);
$db_wild_tag = str_replace("*", "%", $db_wild_tag); return $tag;
$newtags = $database->get_col(
$database->scoreql_to_sql("SELECT tag FROM tags WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(?)"),
array($db_wild_tag)
);
if(count($newtags) > 0) {
$resolved = $newtags;
} else {
$resolved = array($tag);
}
return $resolved;
} }
} }