SqlTools.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. namespace KarmaFW\Database\Sql;
  3. // TODO: a transformer en une classe trait de SqlDb
  4. class SqlTools
  5. {
  6. protected $db;
  7. public function __construct($db)
  8. {
  9. $this->db = $db;
  10. }
  11. public function parseDSN($dsn)
  12. {
  13. // PARSE A DSN LIKE mysql://user:password@host:port/database
  14. // AND RETURNS driver,host,port,user,passwd,db
  15. if (empty($dsn)) {
  16. return [
  17. 'driver' => '',
  18. 'host' => '',
  19. 'port' => '',
  20. 'user' => '',
  21. 'passwd' => '',
  22. 'db' => '',
  23. 'charset' => '',
  24. ];
  25. }
  26. $parts0 = explode('?', $dsn);
  27. $dsn_url = $parts0[0];
  28. $querystring = isset($parts0[1]) ? $parts0[1] : '';
  29. parse_str($querystring, $GET);
  30. $charset = isset($GET['charset']) ? $GET['charset'] : '';
  31. $parts1 = explode(':', $dsn_url);
  32. $driver = $parts1[0];
  33. $parts2 = explode('/', $dsn_url);
  34. $user_passwd_host_port = $parts2[2];
  35. $db = ! empty($parts2[3]) ? $parts2[3] : null;
  36. $parts3 = explode('@', $user_passwd_host_port);
  37. if (count($parts3) > 1) {
  38. // USER (AND OPTIONNALY PASSWORD) IS DEFINED
  39. // mysql://user@host/database
  40. // mysql://user@host:port/database
  41. // mysql://user:password@host/database
  42. // mysql://user:password@host:port/database
  43. $user_password = $parts3[0];
  44. $host_port = $parts3[1];
  45. } else {
  46. // USER AND PASSWORD ARE NOT DEFINED
  47. // mysql://host/database
  48. // mysql://host:port/database
  49. $user_password = '';
  50. $host_port = $parts3[0];
  51. }
  52. $parts4 = explode(':', $host_port);
  53. $host = $parts4[0];
  54. if (count($parts4) > 1) {
  55. // HOST AND PORT ARE DEFINED
  56. // mysql://user@host:port/database
  57. // mysql://user:password@host:port/database
  58. $port = $parts4[1];
  59. } else {
  60. // HOST IS DEFINED. PORT IS NOT DEFINED
  61. // mysql://user@host/database
  62. // mysql://user:password@host/database
  63. $port = 3306;
  64. }
  65. $parts5 = explode(':', $user_password);
  66. $user = $parts5[0];
  67. if (count($parts5) > 1) {
  68. // USER AND PASSWORD ARE DEFINED
  69. // mysql://user:password@host/database
  70. // mysql://user:password@host:port/database
  71. $passwd = $parts5[1];
  72. } else {
  73. // USER IS DEFINED. PASSWORD IS NOT DEFINED
  74. // mysql://user@host/database
  75. // mysql://user@host:port/database
  76. $passwd = '';
  77. }
  78. return [
  79. 'driver' => $driver,
  80. 'host' => $host,
  81. 'port' => $port,
  82. 'user' => $user,
  83. 'passwd' => $passwd,
  84. 'db' => $db,
  85. 'charset' => $charset,
  86. ];
  87. }
  88. public function escape($var)
  89. {
  90. if (! $this->db->isConnected()) {
  91. $this->db->connect();
  92. }
  93. if (is_null($var)) {
  94. return 'NULL';
  95. }
  96. if (is_bool($var)) {
  97. return intval($var);
  98. }
  99. if (is_int($var)) {
  100. return intval($var);
  101. }
  102. if (is_float($var)) {
  103. return floatval($var);
  104. }
  105. return "'" . $this->db->getDriver()->getConn()->real_escape_string($var) . "'";
  106. }
  107. public function buildSqlWhere($where)
  108. {
  109. $where_sql = array("1" => "1");
  110. if (! empty($where)) {
  111. foreach ($where as $key => $value) {
  112. if (is_null($value)) {
  113. $where_sql[] = $key . ' is null';
  114. }else if (is_bool($value) || is_int($value)) {
  115. $where_sql[] = $key . ' = ' . intval($value);
  116. }else if (is_float($value)) {
  117. $where_sql[] = $key . ' = ' . floatval($value);
  118. }else if (is_string($value)) {
  119. $where_sql[] = $key . ' = ' . $this->escape($value);
  120. }else if ($value instanceof \KarmaFW\Database\Sql\SqlLike) {
  121. $where_sql[] = $key . ' like ' . (string) $this->escape($value);
  122. }else if ($value instanceof \KarmaFW\Database\Sql\SqlIn) {
  123. $value = (string) $value;
  124. if (empty($value)) {
  125. $where_sql[] = '0';
  126. } else {
  127. $where_sql[] = $key . ' in (' . $value . ')';
  128. }
  129. }else if ($value instanceof \KarmaFW\Database\Sql\SqlExpr) {
  130. $where_sql[] = $key . ' = ' . (string) $value;
  131. }else if ($value instanceof \KarmaFW\Database\Sql\SqlWhere) {
  132. $where_sql[] = (string) $value;
  133. }else{
  134. //pre($where, 1);
  135. $where_sql[] = $key . ' = ' . $this->escape($value);
  136. //$where_sql[] = $key . ' = ' . (string) $value;
  137. }
  138. }
  139. }
  140. //pre($where_sql, 1);
  141. return implode(" and ", $where_sql);
  142. }
  143. public function buildSqlUpdateValues($values)
  144. {
  145. $values_sql = array();
  146. if (is_object($values)) {
  147. $values = get_object_vars($values);
  148. }
  149. foreach ($values as $key => $value) {
  150. if (is_null($value)) {
  151. $values_sql[] = $key . ' = NULL';
  152. }else if (gettype($value) === 'string') {
  153. $values_sql[] = $key . ' = ' . $this->escape($value);
  154. }else if (gettype($value) === 'boolean') {
  155. $values_sql[] = $key . ' = ' . intval($value);
  156. }else{
  157. $values_sql[] = $key . ' = ' . $value;
  158. }
  159. }
  160. return implode(", ", $values_sql);
  161. }
  162. public function buildSqlInsertValues($values)
  163. {
  164. $fields_sql = array();
  165. $values_sql = array();
  166. if (is_object($values)) {
  167. $values = get_object_vars($values);
  168. }
  169. foreach ($values as $key => $value) {
  170. if (is_null($value)) {
  171. $values_sql[] = 'NULL';
  172. }else if (gettype($value) === 'string') {
  173. $values_sql[] = $this->escape($value);
  174. }else if (gettype($value) === 'boolean') {
  175. $values_sql[] = intval($value);
  176. }else{
  177. $values_sql[] = $value;
  178. }
  179. $fields_sql[] = $key;
  180. }
  181. return array(
  182. 'fields' => implode(', ', $fields_sql),
  183. 'values' => implode(', ', $values_sql),
  184. );
  185. }
  186. public function buildSqlWhereSearch($q='', $search_fields=[], $min_str_length=1, $max_words=10, $all_words_required=false)
  187. {
  188. $db = $this->db;
  189. $select_sum = "(0";
  190. if ($all_words_required) {
  191. $search_where = "(1";
  192. } else {
  193. $search_where = "(0";
  194. }
  195. $q = trim($q);
  196. $pow_pos_word = 0.5; // correspond à l'index du mot parmi tous les mots de la recherche
  197. $pow_pos_field = 0.5; // correspond à l'index du champ (SQL) de recherche parmi tous les champs de recherche
  198. $pow_pos_match = 0.5; // correspond à la position strpos de la chaine recherchée trouvée dans un des champs de recherche
  199. $pow_length_field_value = 0.3; // correspond à la taille (du texte) de la valeur du champ dans lequel on a trouvé le match
  200. $coef_pos_word = 1;
  201. $coef_pos_field = 1;
  202. $coef_pos_match = 1;
  203. $coef_length_field_value = 1;
  204. if ($search_fields && strlen($q) >= $min_str_length) {
  205. $words = explode(" ", $q);
  206. foreach ($words as $word_idx => $word) {
  207. $word_idx_score = pow(max(1, 10 - $word_idx) * $coef_pos_word, $pow_pos_word); // au dela de 10 mots, on compte comme le 10e mot
  208. $w = $db->escape($word);
  209. $w2 = $db->escape("%" . $word . "%");
  210. $conditions_or = [];
  211. $select_sums = [0];
  212. foreach ($search_fields as $field_idx => $field) {
  213. $conditions_or[] = $field . " like " . $w2;
  214. $field_idx_score = pow(max(1, 10 - $field_idx) * $coef_pos_field, $pow_pos_field); // au dela de 10 fields, on compte comme le 10e field
  215. $select_sums[] = "( if( locate(" . $w . ", ifnull(" . $field . ",'') ) > 0, 1, 0 ) * pow(1 / length(" . $field . ") * " . $coef_length_field_value . ", " . floatval($pow_length_field_value) . ") * " . $word_idx_score . " * " . $field_idx_score . " * pow(greatest( 100 - locate(" . $w . ", ifnull(" . $field . ", '')), 1) * " . floatval($coef_pos_match) . ", " . floatval($pow_pos_match) . ") )";
  216. //$select_sums[] = "( if( locate(" . $w . ", ifnull(" . $field . ",'') ) > 0, 1, 0 ) * (1 / length(" . $field . ")) * " . $word_idx_score . " * " . $field_idx_score . " )";
  217. }
  218. $word_condition = "(" . implode(" or ", $conditions_or) . ")";
  219. if ($all_words_required) {
  220. // TODO
  221. $search_where .= " and (" . $word_condition . ")";
  222. } else {
  223. $search_where .= " or " . $word_condition;
  224. }
  225. //$select_sum .= " + if(" . $word_condition . ", " . $word_idx_score . ", 0)";
  226. $select_sum .= " + (" . implode(" + ", $select_sums) . ")";
  227. if (! empty($max_words) && $word_idx >= $max_words) {
  228. break; // one ne prend plus en compte les mots au dela de $max_words
  229. }
  230. }
  231. }
  232. $search_where .= ")";
  233. $select_sum .= ")";
  234. //pre($select_sum, 1);
  235. return [
  236. 'select' => $select_sum,
  237. 'where' => $search_where,
  238. ];
  239. }
  240. public function buildSqlWhereSearch_beta($q='', $search_fields=[], $min_str_length=1, $max_words=10, $all_words_required=false)
  241. {
  242. $db = $this->db;
  243. $q = str_replace(['.', ',', '-', '_', ';', ':', '(', ')', '[', ']'], ' ', $q);
  244. //$q_one = str_replace(' ', '', $q);
  245. $q_len = strlen($q);
  246. /*
  247. $search_fields = [
  248. 'name',
  249. ];
  250. */
  251. $fields_count = count($search_fields);
  252. $words = explode(" ", $q);
  253. $words_count = count($words);
  254. if ($all_words_required) {
  255. $search_where = "(1";
  256. } else {
  257. $search_where = "(0";
  258. }
  259. $fields_scores = [];
  260. // pour chaque champ sql dans lequel on recherche...
  261. foreach ($search_fields as $field_idx => $field) {
  262. $field_pos = $field_idx + 1;
  263. // score de position du champ sql parmis tous les champs où on va rechercher
  264. $field_idx_score = 1 / $field_pos;
  265. $words_scores = [];
  266. // pour chaque mot de l'expression recherchée...
  267. foreach ($words as $word_idx => $word) {
  268. $word_len = strlen($word);
  269. $word_pos = $word_idx + 1;
  270. $w = $db->escape($word);
  271. $w_like = $db->escape("%" . $word . "%");
  272. $w_regex = $db->escape('\b' . preg_quote($word) . '\b');
  273. // score de position parmi les mots de recherche
  274. $word_idx_score = $word_pos / $words_count;
  275. // score de longueur du mot par rapport à la longueur total de l'expression de recherche
  276. $word_search_len_score = $word_len / $q_len;
  277. // score de longueur par rapport a la longueur du champ sql
  278. //$word_len = strlen($word);
  279. $word_field_len_score = "(least($word_len / length($field), length($field) / $word_len))"; // longueur du mot (de recherche) rapport à la longueur du champ sql
  280. // score de position de match dans la valeur du champ sql
  281. $locate_max = "(greatest(0.1, length($field) - $word_len) )";
  282. $word_match_pos_score = "(locate(" . $w . ", $field) / $locate_max)";
  283. // matching
  284. $word_matching_score = "(case when " . $field . " = " . $w . " then 1
  285. when " . $field . " regexp " . $w_regex . " then 0.7
  286. when " . $field . " like " . $w_like . " then 0.5
  287. /*
  288. when soundex(" . $field . ") = soundex(" . $w_like . ") then 0.3
  289. when (abs(mid(soundex(" . $field . "), 2) - mid(soundex(" . $w_like . "), 2)) <= 5 and left(soundex(" . $field . "),1) = left(soundex(" . $w_like . "),1) ) then 0.1
  290. */
  291. else 0
  292. end )";
  293. $word_score = "($field_idx_score * $word_idx_score * $word_search_len_score * $word_field_len_score * $word_match_pos_score * $word_matching_score)";
  294. $words_scores[] = $word_score;
  295. if ($all_words_required) {
  296. // TODO
  297. $search_where .= " and (" . $word_matching_score . ")";
  298. } else {
  299. $search_where .= " or " . $word_matching_score;
  300. }
  301. }
  302. $fields_scores[] = "(" . implode(" + ", $words_scores) . ")";
  303. }
  304. $search_where .= ")";
  305. $search_score = "(" . implode(' + ', $fields_scores) . ")";
  306. if (false) {
  307. echo "SCORE:" . PHP_EOL;
  308. $nb_scores = count($words_scores);
  309. print_r($search_score);
  310. echo PHP_EOL;
  311. echo "WHERE:" . PHP_EOL;
  312. print_r($search_where);
  313. echo PHP_EOL;
  314. exit;
  315. }
  316. return [
  317. 'select' => $search_score,
  318. 'where' => $search_where,
  319. ];
  320. }
  321. }