Changeset 3747:3d9aee789637 for inc
- Timestamp:
- 04/03/18 19:23:37 (7 years ago)
- Branch:
- default
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
inc/core/class.dc.selectstatement.php
r3743 r3747 13 13 14 14 /** 15 * S elect Statement : small utility to build selectqueries15 * SQL Statement : small utility to build SQL queries 16 16 */ 17 class dcS electStatement17 class dcSqlStatement 18 18 { 19 19 protected $core; … … 22 22 protected $columns; 23 23 protected $from; 24 protected $join;25 24 protected $where; 26 25 protected $cond; 27 26 protected $sql; 27 28 /** 29 * Class constructor 30 * 31 * @param dcCore $core dcCore instance 32 * @param mixed $from optional from clause(s) 33 */ 34 public function __construct(&$core, $from = null) 35 { 36 $this->core = &$core; 37 $this->con = &$core->con; 38 39 $this->columns = 40 $this->from = 41 $this->where = 42 $this->cond = 43 $this->sql = 44 array(); 45 46 if ($from !== null) { 47 if (is_array($from)) { 48 $this->froms($from); 49 } else { 50 $this->from($from); 51 } 52 } 53 } 54 55 /** 56 * Magic getter method 57 * 58 * @param string $property The property 59 * 60 * @return mixed property value if property exists 61 */ 62 public function __get($property) 63 { 64 if (property_exists($this, $property)) { 65 return $this->$property; 66 } 67 trigger_error('Unknown property ' . $property, E_USER_ERROR); 68 return; 69 } 70 71 /** 72 * Magic setter method 73 * 74 * @param string $property The property 75 * @param mixed $value The value 76 * 77 * @return self 78 */ 79 public function __set($property, $value) 80 { 81 if (property_exists($this, $property)) { 82 $this->$property = $value; 83 } else { 84 trigger_error('Unknown property ' . $property, E_USER_ERROR); 85 } 86 return $this; 87 } 88 89 /** 90 * Adds column(s) 91 * 92 * @param mixed $c the column(s) 93 * @param boolean $reset reset previous column(s) first 94 * 95 * @return dcSelectStatement self instance, enabling to chain calls 96 */ 97 public function columns($c, $reset = false) 98 { 99 if ($reset) { 100 $this->columns = array(); 101 } 102 if (is_array($c)) { 103 $this->columns = array_merge($this->columns, $c); 104 } else { 105 array_push($this->columns, $c); 106 } 107 return $this; 108 } 109 110 /** 111 * columns() alias 112 * 113 * @param mixed $c the column(s) 114 * @param boolean $reset reset previous column(s) first 115 * 116 * @return dcSelectStatement self instance, enabling to chain calls 117 */ 118 public function column($c, $reset = false) 119 { 120 return $this->columns($c, $reset); 121 } 122 123 /** 124 * Adds FROM clause(s) 125 * 126 * @param mixed $c the from clause(s) 127 * @param boolean $reset reset previous from(s) first 128 * 129 * @return dcSelectStatement self instance, enabling to chain calls 130 */ 131 public function from($c, $reset = false) 132 { 133 if ($reset) { 134 $this->from = array(); 135 } 136 if (is_array($c)) { 137 $filter = function($v) { 138 return trim(ltrim($v, ',')); 139 }; 140 $c = array_map($filter, $c); // Cope with legacy code 141 $this->from = array_merge($this->from, $c); 142 } else { 143 $c = trim(ltrim($c, ',')); // Cope with legacy code 144 array_push($this->from, $c); 145 } 146 return $this; 147 } 148 149 /** 150 * Adds WHERE clause(s) condition (each will be AND combined in statement) 151 * 152 * @param mixed $c the clause(s) 153 * @param boolean $reset reset previous where(s) first 154 * 155 * @return dcSelectStatement self instance, enabling to chain calls 156 */ 157 public function where($c, $reset = false) 158 { 159 if ($reset) { 160 $this->where = array(); 161 } 162 if (is_array($c)) { 163 $this->where = array_merge($this->where, $c); 164 } else { 165 array_push($this->where, $c); 166 } 167 return $this; 168 } 169 170 /** 171 * Adds additional WHERE clause condition(s) (including an operator at beginning) 172 * 173 * @param mixed $c the clause(s) 174 * @param boolean $reset reset previous condition(s) first 175 * 176 * @return dcSelectStatement self instance, enabling to chain calls 177 */ 178 public function cond($c, $reset = false) 179 { 180 if ($reset) { 181 $this->cond = array(); 182 } 183 if (is_array($c)) { 184 $this->cond = array_merge($this->cond, $c); 185 } else { 186 array_push($this->cond, $c); 187 } 188 return $this; 189 } 190 191 /** 192 * Adds generic clause(s) 193 * 194 * @param mixed $c the clause(s) 195 * @param boolean $reset reset previous generic clause(s) first 196 * 197 * @return dcSelectStatement self instance, enabling to chain calls 198 */ 199 public function sql($c, $reset = false) 200 { 201 if ($reset) { 202 $this->sql = array(); 203 } 204 if (is_array($c)) { 205 $this->sql = array_merge($this->sql, $c); 206 } else { 207 array_push($this->sql, $c); 208 } 209 return $this; 210 } 211 212 // Helpers 213 214 /** 215 * Escape a value 216 * 217 * @param string $value The value 218 * 219 * @return string 220 */ 221 public function escape($value) 222 { 223 return $this->con->escape($value); 224 } 225 226 /** 227 * Quote and escape a value if necessary (type string) 228 * 229 * @param mixed $value The value 230 * @param boolean $escape The escape 231 * 232 * @return string 233 */ 234 public function quote($value, $escape = true) 235 { 236 return 237 (is_string($value) ? "'" : '') . 238 ($escape ? $this->con->escape($value) : $value) . 239 (is_string($value) ? "'" : ''); 240 } 241 242 /** 243 * Return an SQL IN (…) fragment 244 * 245 * @param mixed $list The list 246 * 247 * @return string 248 */ 249 public function in($list) 250 { 251 return $this->con->in($list); 252 } 253 254 /** 255 * Return an SQL formatted date 256 * 257 * @param string $field Field name 258 * @param string $pattern Date format 259 * 260 * @return string 261 */ 262 public function dateFormat($field, $pattern) 263 { 264 return $this->con->dateFormat($field, $pattern); 265 } 266 267 /** 268 * Return an SQL formatted REGEXP clause 269 * 270 * @param string $value The value 271 * 272 * @return string 273 */ 274 public function regexp($value) 275 { 276 if ($this->con->driver() == 'mysql' || $this->con->driver() == 'mysqli' || $this->con->driver() == 'mysqlimb4') { 277 $clause = "REGEXP '^" . $this->escape(preg_quote($value)) . "[0-9]+$'"; 278 } elseif ($this->con->driver() == 'pgsql') { 279 $clause = "~ '^" . $this->escape(preg_quote($value)) . "[0-9]+$'"; 280 } else { 281 $clause = "LIKE '" . 282 $sql->escape(preg_replace(array('%', '_', '!'), array('!%', '!_', '!!'), $value)) . 283 "%' ESCAPE '!'"; 284 } 285 return $clause; 286 } 287 288 /** 289 * Compare two SQL queries 290 * 291 * May be used for debugging purpose as: 292 * 293 * if (!$sql->isSame($sql->statement(), $oldRequest)) { 294 * trigger_error('SQL statement error: ' . $sql->statement() . ' / ' .$oldRequest, E_USER_ERROR); 295 * } 296 * 297 * @param string $local The local 298 * @param string $external The external 299 * 300 * @return boolean True if same, False otherwise. 301 */ 302 public function isSame($local, $external) 303 { 304 $filter = function ($s) { 305 $s = strtoupper($s); 306 $patterns = array( 307 '\s+' => ' ', // Multiple spaces/tabs -> one space 308 ' \)' => ')', // <space>) -> ) 309 ' ,' => ',', // <space>, -> , 310 '\( ' => '(' // (<space> -> ( 311 ); 312 foreach ($patterns as $pattern => $replace) { 313 $s = preg_replace('!' . $pattern . '!', $replace, $s); 314 } 315 return trim($s); 316 }; 317 return ($filter($local) === $filter($external)); 318 } 319 } 320 321 /** 322 * Select Statement : small utility to build select queries 323 */ 324 class dcSelectStatement extends dcSqlStatement 325 { 326 protected $join; 28 327 protected $having; 29 328 protected $order; … … 41 340 public function __construct(&$core, $from = null) 42 341 { 43 $this->core = &$core;44 $this->con = &$core->con;45 46 $this->columns =47 $this->from =48 342 $this->join = 49 $this->where =50 $this->cond =51 $this->sql =52 343 $this->having = 53 344 $this->order = 54 345 $this->group = 55 346 array(); 347 56 348 $this->limit = null; 57 349 $this->offset = null; 58 350 $this->distinct = false; 59 351 60 if ($from !== null) { 61 if (is_array($from)) { 62 $this->froms($from); 63 } else { 64 $this->from($from); 65 } 66 } 67 } 68 69 /** 70 * Magic getter method 71 * 72 * @param string $property The property 73 * 74 * @return mixed property value if property exists 75 */ 76 public function __get($property) 77 { 78 if (property_exists($this, $property)) { 79 return $this->$property; 80 } 81 trigger_error('Unknown property ' . $property, E_USER_ERROR); 82 return; 83 } 84 85 /** 86 * Magic setter method 87 * 88 * @param string $property The property 89 * @param mixed $value The value 90 * 91 * @return self 92 */ 93 public function __set($property, $value) 94 { 95 if (property_exists($this, $property)) { 96 $this->$property = $value; 97 } else { 98 trigger_error('Unknown property ' . $property, E_USER_ERROR); 99 } 100 return $this; 101 } 102 103 /** 104 * Adds column(s) 105 * 106 * @param mixed $c the column(s) 107 * @param boolean $reset reset previous column(s) first 108 * 109 * @return dcSelectStatement self instance, enabling to chain calls 110 */ 111 public function columns($c, $reset = false) 112 { 113 if ($reset) { 114 $this->columns = array(); 115 } 116 if (is_array($c)) { 117 $this->columns = array_merge($this->columns, $c); 118 } else { 119 array_push($this->columns, $c); 120 } 121 return $this; 122 } 123 124 /** 125 * columns() alias 126 * 127 * @param mixed $c the column(s) 128 * @param boolean $reset reset previous column(s) first 129 * 130 * @return dcSelectStatement self instance, enabling to chain calls 131 */ 132 public function column($c, $reset = false) 133 { 134 return $this->columns($c, $reset); 135 } 136 137 /** 138 * Adds FROM clause(s) 139 * 140 * @param mixed $c the from clause(s) 141 * @param boolean $reset reset previous from(s) first 142 * 143 * @return dcSelectStatement self instance, enabling to chain calls 144 */ 145 public function from($c, $reset = false) 146 { 147 if ($reset) { 148 $this->from = array(); 149 } 150 if (is_array($c)) { 151 $filter = function($v) { 152 return trim(ltrim($v, ',')); 153 }; 154 $c = array_map($filter, $c); // Cope with legacy code 155 $this->from = array_merge($this->from, $c); 156 } else { 157 $c = trim(ltrim($c, ',')); // Cope with legacy code 158 array_push($this->from, $c); 159 } 160 return $this; 352 parent::__construct($core, $from); 161 353 } 162 354 … … 178 370 } else { 179 371 array_push($this->join, $c); 180 }181 return $this;182 }183 184 /**185 * Adds WHERE clause(s) condition (each will be AND combined in statement)186 *187 * @param mixed $c the clause(s)188 * @param boolean $reset reset previous where(s) first189 *190 * @return dcSelectStatement self instance, enabling to chain calls191 */192 public function where($c, $reset = false)193 {194 if ($reset) {195 $this->where = array();196 }197 if (is_array($c)) {198 $this->where = array_merge($this->where, $c);199 } else {200 array_push($this->where, $c);201 }202 return $this;203 }204 205 /**206 * Adds additional WHERE clause condition(s) (including an operator at beginning)207 *208 * @param mixed $c the clause(s)209 * @param boolean $reset reset previous condition(s) first210 *211 * @return dcSelectStatement self instance, enabling to chain calls212 */213 public function cond($c, $reset = false)214 {215 if ($reset) {216 $this->cond = array();217 }218 if (is_array($c)) {219 $this->cond = array_merge($this->cond, $c);220 } else {221 array_push($this->cond, $c);222 }223 return $this;224 }225 226 /**227 * Adds generic clause(s)228 *229 * @param mixed $c the clause(s)230 * @param boolean $reset reset previous generic clause(s) first231 *232 * @return dcSelectStatement self instance, enabling to chain calls233 */234 public function sql($c, $reset = false)235 {236 if ($reset) {237 $this->sql = array();238 }239 if (is_array($c)) {240 $this->sql = array_merge($this->sql, $c);241 } else {242 array_push($this->sql, $c);243 372 } 244 373 return $this; … … 360 489 } 361 490 362 // Helpers363 364 /**365 * Escape a value366 *367 * @param string $value The value368 *369 * @return string370 */371 public function escape($value)372 {373 return $this->con->escape($value);374 }375 376 /**377 * Return an SQL IN (…) fragment378 *379 * @param mixed $list The list380 *381 * @return string382 */383 public function in($list)384 {385 return $this->con->in($list);386 }387 388 /**389 * Return an SQL formatted date390 *391 * @param string $field Field name392 * @param string $pattern Date format393 *394 * @return string395 */396 public function dateFormat($field, $pattern)397 {398 return $this->con->dateFormat($field, $pattern);399 }400 401 /**402 * Return an SQL formatted REGEXP clause403 *404 * @param string $value The value405 *406 * @return string407 */408 public function regexp($value)409 {410 if ($this->con->driver() == 'mysql' || $this->con->driver() == 'mysqli' || $this->con->driver() == 'mysqlimb4') {411 $clause = "REGEXP '^" . $this->escape(preg_quote($value)) . "[0-9]+$'";412 } elseif ($this->con->driver() == 'pgsql') {413 $clause = "~ '^" . $this->escape(preg_quote($value)) . "[0-9]+$'";414 } else {415 $clause = "LIKE '" .416 $sql->escape(preg_replace(array('%', '_', '!'), array('!%', '!_', '!!'), $value)) .417 "%' ESCAPE '!'";418 }419 return $clause;420 }421 422 /**423 * Quote and escape a value if necessary (type string)424 *425 * @param mixed $value The value426 * @param boolean $escape The escape427 *428 * @return string429 */430 public function quote($value, $escape = true)431 {432 return433 (is_string($value) ? "'" : '') .434 ($escape ? $this->con->escape($value) : $value) .435 (is_string($value) ? "'" : '');436 }437 438 491 /** 439 492 * Returns the select statement … … 512 565 return trim($query); 513 566 } 514 515 /** 516 * Compare two SQL queries 517 * 518 * May be used for debugging purpose as: 519 * if (!$sql->isSame($sql->statement(), $oldRequest)) { 520 * trigger_error('SQL statement error', E_USER_ERROR); 521 * } 522 * 523 * @param string $local The local 524 * @param string $external The external 525 * 526 * @return boolean True if same, False otherwise. 527 */ 528 public function isSame($local, $external) 529 { 530 $filter = function ($s) { 531 $s = strtoupper($s); 532 $patterns = array( 533 '\s+' => ' ', // Multiple spaces/tabs -> one space 534 ' \)' => ')', // <space>) -> ) 535 ' ,' => ',', // <space>, -> , 536 '\( ' => '(' // (<space> -> ( 537 ); 538 foreach ($patterns as $pattern => $replace) { 539 $s = preg_replace('!' . $pattern . '!', $replace, $s); 567 } 568 569 /** 570 * Delete Statement : small utility to build delete queries 571 */ 572 class dcDeleteStatement extends dcSqlStatement 573 { 574 /** 575 * Returns the delete statement 576 * 577 * @return string the statement 578 */ 579 public function statement() 580 { 581 // Check if source given 582 if (!count($this->from)) { 583 trigger_error(__('SQL DELETE requires a FROM source'), E_USER_ERROR); 584 return ''; 585 } 586 587 // Query 588 $query = 'DELETE '; 589 590 // Table 591 $query .= 'FROM ' . $this->from[0] . ' '; 592 593 // Where clause(s) 594 if (count($this->where)) { 595 $query .= 'WHERE ' . join(' AND ', $this->where) . ' '; 596 } 597 598 // Direct where clause(s) 599 if (count($this->cond)) { 600 if (!count($this->where)) { 601 $query .= 'WHERE 1 '; // Hack to cope with the operator included in top of each condition 540 602 } 541 return $s; 542 }; 543 return ($filter($local) === $filter($external)); 603 $query .= join(' ', $this->cond) . ' '; 604 } 605 606 // Generic clause(s) 607 if (count($this->sql)) { 608 $query .= join(' ', $this->sql) . ' '; 609 } 610 611 return trim($query); 544 612 } 545 613 } 614 615 /** 616 * Update Statement : small utility to build update queries 617 */ 618 class dcUpdateStatement extends dcSqlStatement 619 { 620 protected $set; 621 622 /** 623 * Class constructor 624 * 625 * @param dcCore $core dcCore instance 626 * @param mixed $from optional from clause(s) 627 */ 628 public function __construct(&$core, $from = null) 629 { 630 $this->set = array(); 631 632 parent::__construct($core, $from); 633 } 634 635 /** 636 * from() alias 637 * 638 * @param mixed $c the reference clause(s) 639 * @param boolean $reset reset previous reference first 640 * 641 * @return dcUpdateStatement self instance, enabling to chain calls 642 */ 643 public function reference($c, $reset = false) 644 { 645 return $this->from($c, $reset); 646 } 647 648 /** 649 * from() alias 650 * 651 * @param mixed $c the reference clause(s) 652 * @param boolean $reset reset previous reference first 653 * 654 * @return dcUpdateStatement self instance, enabling to chain calls 655 */ 656 public function ref($c, $reset = false) 657 { 658 return $this->reference($c, $reset); 659 } 660 661 /** 662 * Adds update value(s) 663 * 664 * @param mixed $c the udpate values(s) 665 * @param boolean $reset reset previous update value(s) first 666 * 667 * @return dcUpdateStatement self instance, enabling to chain calls 668 */ 669 public function set($c, $reset = false) 670 { 671 if ($reset) { 672 $this->set = array(); 673 } 674 if (is_array($c)) { 675 $this->set = array_merge($this->set, $c); 676 } else { 677 array_push($this->set, $c); 678 } 679 return $this; 680 } 681 682 /** 683 * set() alias 684 * 685 * @param mixed $c the update value(s) 686 * @param boolean $reset reset previous update value(s) first 687 * 688 * @return dcUpdateStatement self instance, enabling to chain calls 689 */ 690 public function sets($c, $reset = false) 691 { 692 return $this->set($c, $reset); 693 } 694 695 /** 696 * Returns the WHERE part of update statement 697 * 698 * Useful to construct the where clause used with cursor->update() method 699 */ 700 public function whereStatement() 701 { 702 $query = ''; 703 704 // Where clause(s) 705 if (count($this->where)) { 706 $query .= 'WHERE ' . join(' AND ', $this->where) . ' '; 707 } 708 709 // Direct where clause(s) 710 if (count($this->cond)) { 711 if (!count($this->where)) { 712 $query .= 'WHERE 1 '; // Hack to cope with the operator included in top of each condition 713 } 714 $query .= join(' ', $this->cond) . ' '; 715 } 716 717 // Generic clause(s) 718 if (count($this->sql)) { 719 $query .= join(' ', $this->sql) . ' '; 720 } 721 722 return trim($query); 723 } 724 725 /** 726 * Returns the update statement 727 * 728 * @return string the statement 729 */ 730 public function statement() 731 { 732 // Check if source given 733 if (!count($this->from)) { 734 trigger_error(__('SQL UPDATE requires an INTO source'), E_USER_ERROR); 735 return ''; 736 } 737 738 // Query 739 $query = 'UPDATE '; 740 741 // Reference 742 $query .= $this->from[0] . ' '; 743 744 // Value(s) 745 if (count($this->set)) { 746 $query .= 'SET ' . join(', ', $this->set) . ' '; 747 } 748 749 // Where clause(s) 750 if (count($this->where)) { 751 $query .= 'WHERE ' . join(' AND ', $this->where) . ' '; 752 } 753 754 // Direct where clause(s) 755 if (count($this->cond)) { 756 if (!count($this->where)) { 757 $query .= 'WHERE 1 '; // Hack to cope with the operator included in top of each condition 758 } 759 $query .= join(' ', $this->cond) . ' '; 760 } 761 762 // Generic clause(s) 763 if (count($this->sql)) { 764 $query .= join(' ', $this->sql) . ' '; 765 } 766 767 return trim($query); 768 } 769 } 770 771 /** 772 * Insert Statement : small utility to build insert queries 773 */ 774 class dcInsertStatement extends dcSqlStatement 775 { 776 protected $lines; 777 778 /** 779 * Class constructor 780 * 781 * @param dcCore $core dcCore instance 782 * @param mixed $into optional into clause(s) 783 */ 784 public function __construct(&$core, $into = null) 785 { 786 $this->lines = array(); 787 788 parent::__construct($core, $into); 789 } 790 791 /** 792 * from() alias 793 * 794 * @param mixed $c the into clause(s) 795 * @param boolean $reset reset previous into first 796 * 797 * @return dcSelectStatement self instance, enabling to chain calls 798 */ 799 public function into($c, $reset = false) 800 { 801 return $this->into($c, $reset); 802 } 803 804 /** 805 * Adds update value(s) 806 * 807 * @param mixed $c the insert values(s) 808 * @param boolean $reset reset previous insert value(s) first 809 * 810 * @return dcSelectStatement self instance, enabling to chain calls 811 */ 812 public function lines($c, $reset = false) 813 { 814 if ($reset) { 815 $this->lines = array(); 816 } 817 if (is_array($c)) { 818 $this->lines = array_merge($this->lines, $c); 819 } else { 820 array_push($this->lines, $c); 821 } 822 return $this; 823 } 824 825 /** 826 * line() alias 827 * 828 * @param mixed $c the insert value(s) 829 * @param boolean $reset reset previous insert value(s) first 830 * 831 * @return dcInsertStatement self instance, enabling to chain calls 832 */ 833 public function line($c, $reset = false) 834 { 835 return $this->lines($c, $reset); 836 } 837 838 /** 839 * Returns the insert statement 840 * 841 * @return string the statement 842 */ 843 public function statement() 844 { 845 // Check if source given 846 if (!count($this->from)) { 847 trigger_error(__('SQL INSERT requires an INTO source'), E_USER_ERROR); 848 return ''; 849 } 850 851 // Query 852 $query = 'INSERT '; 853 854 // Reference 855 $query .= 'INTO ' . $this->from[0] . ' '; 856 857 // Column(s) 858 if (count($this->columns)) { 859 $query .= '(' . join(', ', $this->columns) . ') '; 860 } 861 862 // Value(s) 863 $query .= 'VALUES '; 864 if (count($this->lines)) { 865 $raws = array(); 866 foreach ($this->lines as $line) { 867 $raws[] = '(' . join(', ', $line) . ')'; 868 } 869 $query .= join(', ', $raws); 870 } else { 871 // Use SQL default values (useful only if SQL strict mode is off or if every columns has a defined default value) 872 $query .= '()'; 873 } 874 875 return trim($query); 876 } 877 }
Note: See TracChangeset
for help on using the changeset viewer.