Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2_Queries.php |
— | — | @@ -77,7 +77,7 @@ |
78 | 78 | * @param $concept Title |
79 | 79 | */ |
80 | 80 | public function refreshConceptCache($concept) { |
81 | | - global $smwgQMaxLimit, $smwgQConceptFeatures; |
| 81 | + global $smwgQMaxLimit, $smwgQConceptFeatures, $wgDBtype; |
82 | 82 | $cid = $this->m_store->getSMWPageID($concept->getDBKey(), SMW_NS_CONCEPT, ''); |
83 | 83 | $cid_c = $this->m_store->getSMWPageID($concept->getDBKey(), SMW_NS_CONCEPT, '', false); |
84 | 84 | if ($cid != $cid_c) { |
— | — | @@ -106,10 +106,18 @@ |
107 | 107 | } |
108 | 108 | // Update database: |
109 | 109 | $this->m_dbs->delete('smw_conccache', array('o_id' => $cid), 'SMW::refreshConceptCache'); |
110 | | - $this->m_dbs->query("INSERT IGNORE INTO " . $this->m_dbs->tableName('smw_conccache') . |
| 110 | + if ($wgDBtype=='postgres') { // PostgresQL: no INSERT IGNORE, check for duplicates explicitly |
| 111 | + $where = $qobj->where . ($qobj->where?' AND ':'') . |
| 112 | + 'NOT EXISTS (SELECT NULL FROM ' . $this->m_dbs->tableName('smw_conccache') . |
| 113 | + ' WHERE ' . $this->m_dbs->tablename('smw_conccache') . '.s_id = ' . $qobj->alias . '.s_id ' . |
| 114 | + ' AND ' . $this->m_dbs->tablename('smw_conccache') . '.o_id = ' . $qobj->alias . '.o_id )'; |
| 115 | + } else { // MySQL just uses INSERT IGNORE, no extra conditions |
| 116 | + $where = $qobj->where; |
| 117 | + } |
| 118 | + $this->m_dbs->query("INSERT " . (($wgDBtype=='postgres')?"":"IGNORE ") . "INTO " . $this->m_dbs->tableName('smw_conccache') . |
111 | 119 | " SELECT DISTINCT $qobj->joinfield AS s_id, $cid AS o_id FROM " . |
112 | 120 | $this->m_dbs->tableName($qobj->jointable) . " AS $qobj->alias" . $qobj->from . |
113 | | - ($qobj->where?" WHERE ":'') . $qobj->where . " LIMIT $smwgQMaxLimit", |
| 121 | + ($where?" WHERE ":'') . $where . " LIMIT $smwgQMaxLimit", |
114 | 122 | 'SMW::refreshConceptCache'); |
115 | 123 | $this->m_dbs->update('smw_conc2', array('cache_date' => strtotime("now"), 'cache_count' => $this->m_dbs->affectedRows()), array('s_id' => $cid), 'SMW::refreshConceptCache'); |
116 | 124 | } else { // just delete old data if there is any |
— | — | @@ -263,7 +271,10 @@ |
264 | 272 | return $result; |
265 | 273 | } |
266 | 274 | $sql_options = $this->getSQLOptions($query,$rootid); |
267 | | - $res = $this->m_dbs->select($this->m_dbs->tableName($qobj->jointable) . " AS $qobj->alias" . $qobj->from, "DISTINCT $qobj->alias.smw_id AS id,$qobj->alias.smw_title AS t,$qobj->alias.smw_namespace AS ns,$qobj->alias.smw_iw AS iw", $qobj->where, 'SMW::getQueryResult', $sql_options); |
| 275 | + $sortfields = implode($qobj->sortfields,','); // also select those, required in standard SQL (though MySQL is quite about it) |
| 276 | + $res = $this->m_dbs->select($this->m_dbs->tableName($qobj->jointable) . " AS $qobj->alias" . $qobj->from, |
| 277 | + "DISTINCT $qobj->alias.smw_id AS id,$qobj->alias.smw_title AS t,$qobj->alias.smw_namespace AS ns,$qobj->alias.smw_iw AS iw" |
| 278 | + . ($sortfields?',':'') . $sortfields, $qobj->where, 'SMW::getQueryResult', $sql_options); |
268 | 279 | |
269 | 280 | $qr = array(); |
270 | 281 | $count = 0; |
— | — | @@ -592,6 +603,7 @@ |
593 | 604 | * the actual result. |
594 | 605 | */ |
595 | 606 | protected function executeQueries(SMWSQLStore2Query &$query) { |
| 607 | + global $wgDBtype; |
596 | 608 | switch ($query->type) { |
597 | 609 | case SMW_SQL2_TABLE: // normal query with conjunctive subcondition |
598 | 610 | foreach ($query->components as $qid => $joinfield) { |
— | — | @@ -656,8 +668,7 @@ |
657 | 669 | break; |
658 | 670 | case SMW_SQL2_DISJUNCTION: |
659 | 671 | if ($this->m_qmode !== SMWQuery::MODE_DEBUG) { |
660 | | - $this->m_dbs->query( "CREATE TEMPORARY TABLE " . $this->m_dbs->tableName($query->alias) . |
661 | | - ' ( id INT UNSIGNED KEY ) TYPE=MEMORY', 'SMW::executeQueries' ); |
| 672 | + $this->m_dbs->query($this->getCreateTempIDTableSQL($this->m_dbs->tableName($query->alias)), 'SMW::executeQueries'); |
662 | 673 | } |
663 | 674 | $this->m_querylog[$query->alias] = array(); |
664 | 675 | foreach ($query->components as $qid => $joinfield) { |
— | — | @@ -665,8 +676,10 @@ |
666 | 677 | $this->executeQueries($subquery); |
667 | 678 | $sql = ''; |
668 | 679 | if ($subquery->jointable != '') { |
669 | | - $sql = "INSERT IGNORE INTO " . $this->m_dbs->tableName($query->alias) . " SELECT $subquery->joinfield FROM " . |
670 | | - $this->m_dbs->tableName($subquery->jointable) . " AS $subquery->alias $subquery->from" . ($subquery->where?" WHERE $subquery->where":''); |
| 680 | + $sql = 'INSERT ' . (($wgDBtype=='postgres')?'':'IGNORE ') . 'INTO ' . |
| 681 | + $this->m_dbs->tableName($query->alias) . |
| 682 | + " SELECT $subquery->joinfield FROM " . $this->m_dbs->tableName($subquery->jointable) . |
| 683 | + " AS $subquery->alias $subquery->from" . ($subquery->where?" WHERE $subquery->where":''); |
671 | 684 | } elseif ($subquery->joinfield !== '') { |
672 | 685 | /// NOTE: this works only for single "unconditional" values without further |
673 | 686 | /// WHERE or FROM. The execution must take care of not creating any others. |
— | — | @@ -674,7 +687,7 @@ |
675 | 688 | foreach ($subquery->joinfield as $value) { |
676 | 689 | $values .= ($values?',':'') . '(' . $this->m_dbs->addQuotes($value) . ')'; |
677 | 690 | } |
678 | | - $sql = "INSERT IGNORE INTO " . $this->m_dbs->tableName($query->alias) . " (id) VALUES $values"; |
| 691 | + $sql = 'INSERT ' . (($wgDBtype=='postgres')?'':'IGNORE ') . 'INTO ' . $this->m_dbs->tableName($query->alias) . " (id) VALUES $values"; |
679 | 692 | } // else: // interpret empty joinfields as impossible condition (empty result), ignore |
680 | 693 | if ($sql) { |
681 | 694 | $this->m_querylog[$query->alias][] = $sql; |
— | — | @@ -700,6 +713,7 @@ |
701 | 714 | * and temporary tables are used in many cases. |
702 | 715 | */ |
703 | 716 | protected function executeHierarchyQuery(&$query) { |
| 717 | + global $wgDBtype; |
704 | 718 | $fname = "SMWSQLStore2Queries::executeQueries-hierarchy-$query->type (SMW)"; |
705 | 719 | wfProfileIn($fname); |
706 | 720 | global $smwgQSubpropertyDepth, $smwgQSubcategoryDepth; |
— | — | @@ -733,13 +747,12 @@ |
734 | 748 | wfProfileOut($fname); |
735 | 749 | return; // no real queries in debug mode |
736 | 750 | } |
| 751 | + $this->m_dbs->query($this->getCreateTempIDTableSQL($tablename), 'SMW::executeHierarchyQuery'); |
737 | 752 | |
738 | | - $this->m_dbs->query( "CREATE TEMPORARY TABLE $tablename " . |
739 | | - '( id INT UNSIGNED NOT NULL KEY) TYPE=MEMORY', 'SMW::executeQueries' ); |
740 | 753 | if (array_key_exists($values, $this->m_hierarchies)) { // just copy known result |
741 | 754 | $this->m_dbs->query("INSERT INTO $tablename (id) SELECT id" . |
742 | 755 | ' FROM ' . $this->m_hierarchies[$values], |
743 | | - 'SMW::executeQueries'); |
| 756 | + 'SMW::executeHierarchyQuery'); |
744 | 757 | wfProfileOut($fname); |
745 | 758 | return; |
746 | 759 | } |
— | — | @@ -750,33 +763,31 @@ |
751 | 764 | /// obtained in the previous step are relevant. So this is a performance measure. |
752 | 765 | $tmpnew = 'smw_new'; |
753 | 766 | $tmpres = 'smw_res'; |
754 | | - $this->m_dbs->query( "CREATE TEMPORARY TABLE $tmpnew " . |
755 | | - '( id INT UNSIGNED ) TYPE=MEMORY', 'SMW::executeQueries' ); |
756 | | - $this->m_dbs->query( "CREATE TEMPORARY TABLE $tmpres " . |
757 | | - '( id INT UNSIGNED ) TYPE=MEMORY', 'SMW::executeQueries' ); |
| 767 | + $this->m_dbs->query($this->getCreateTempIDTableSQL($tmpnew), 'SMW::executeQueries'); |
| 768 | + $this->m_dbs->query($this->getCreateTempIDTableSQL($tmpres), 'SMW::executeQueries'); |
| 769 | + $this->m_dbs->query("INSERT " . (($wgDBtype=='postgres')?"":"IGNORE") . " INTO $tablename (id) VALUES $values", 'SMW::executeHierarchyQuery'); |
| 770 | + $this->m_dbs->query("INSERT " . (($wgDBtype=='postgres')?"":"IGNORE") . " INTO $tmpnew (id) VALUES $values", 'SMW::executeHierarchyQuery'); |
758 | 771 | |
759 | | - $this->m_dbs->query("INSERT IGNORE INTO $tablename (id) VALUES $values", 'SMW::executeQueries'); |
760 | | - $this->m_dbs->query("INSERT IGNORE INTO $tmpnew (id) VALUES $values", 'SMW::executeQueries'); |
761 | | - |
762 | 772 | for ($i=0; $i<$depth; $i++) { |
763 | | - $this->m_dbs->query("INSERT INTO $tmpres (id) SELECT s_id FROM $smw_subs2, $tmpnew WHERE o_id=id", |
764 | | - 'SMW::executeQueries'); |
| 773 | + $this->m_dbs->query("INSERT INTO $tmpres (id) SELECT s_id" . ($wgDBtype=='postgres'?'::integer':'') . " FROM $smw_subs2, $tmpnew WHERE o_id=id", |
| 774 | + 'SMW::executeHierarchyQuery'); |
765 | 775 | if ($this->m_dbs->affectedRows() == 0) { // no change, exit loop |
766 | 776 | break; |
767 | 777 | } |
768 | | - $this->m_dbs->query("INSERT IGNORE INTO $tablename (id) SELECT $tmpres.id FROM $tmpres", |
769 | | - 'SMW::executeQueries'); |
| 778 | + $this->m_dbs->query("INSERT " . (($wgDBtype=='postgres')?'':'IGNORE ') . "INTO $tablename (id) SELECT $tmpres.id FROM $tmpres", |
| 779 | + 'SMW::executeHierarchyQuery'); |
770 | 780 | if ($this->m_dbs->affectedRows() == 0) { // no change, exit loop |
771 | 781 | break; |
772 | 782 | } |
773 | | - $this->m_dbs->query('TRUNCATE TABLE ' . $tmpnew, 'SMW::executeQueries'); // empty "new" table |
| 783 | + /// TODO Why does PostgreSQL CASCADE here? |
| 784 | + $this->m_dbs->query('TRUNCATE TABLE ' . $tmpnew . (($wgDBtype=='postgres')?' CASCADE':'') , 'SMW::executeHierarchyQuery'); // empty "new" table |
774 | 785 | $tmpname = $tmpnew; |
775 | 786 | $tmpnew = $tmpres; |
776 | 787 | $tmpres = $tmpname; |
777 | 788 | } |
778 | 789 | $this->m_hierarchies[$values] = $tablename; |
779 | | - $this->m_dbs->query('DROP TEMPORARY TABLE smw_new', 'SMW::executeQueries'); |
780 | | - $this->m_dbs->query('DROP TEMPORARY TABLE smw_res', 'SMW::executeQueries'); |
| 790 | + $this->m_dbs->query((($wgDBtype=='postgres')?'DROP TABLE IF EXISTS smw_new':'DROP TEMPORARY TABLE smw_new'), 'SMW::executeHierarchyQuery'); |
| 791 | + $this->m_dbs->query((($wgDBtype=='postgres')?'DROP TABLE IF EXISTS smw_res':'DROP TEMPORARY TABLE smw_res'), 'SMW::executeHierarchyQuery'); |
781 | 792 | wfProfileOut($fname); |
782 | 793 | } |
783 | 794 | |
— | — | @@ -849,13 +860,44 @@ |
850 | 861 | |
851 | 862 | /** |
852 | 863 | * After querying, make sure no temporary database tables are left. |
| 864 | + * Postgres will eventually clean up everything at the end of the transaction (" ON COMMIT DROP "). |
853 | 865 | */ |
854 | 866 | protected function cleanUp() { |
| 867 | + global $wgDBtype; |
855 | 868 | if ($this->m_qmode !== SMWQuery::MODE_DEBUG) { |
856 | 869 | foreach ($this->m_querylog as $table => $log) { |
857 | | - $this->m_dbs->query("DROP TEMPORARY TABLE " . $this->m_dbs->tableName($table), 'SMW::getQueryResult'); |
| 870 | + $this->m_dbs->query((($wgDBtype=='postgres')?"DROP TABLE IF EXISTS ":"DROP TEMPORARY TABLE ") . $this->m_dbs->tableName($table), 'SMW::getQueryResult'); |
858 | 871 | } |
859 | 872 | } |
860 | 873 | } |
861 | 874 | |
| 875 | + /** |
| 876 | + * Get SQL code suitable to create a temporary table of the given name, used to store ids. |
| 877 | + * MySQL can do that simply by creating new temporary tables. PostgreSQL first checks if such |
| 878 | + * a table exists. Also, PostgreSQL tables will use a RULE to achieve built-in duplicate |
| 879 | + * elimination. The latter is done using INSERT IGNORE in MySQL. |
| 880 | + * @todo Should the check for table existence in PostgreSQL be otptional? Is it really needed? |
| 881 | + */ |
| 882 | + protected function getCreateTempIDTableSQL($tablename) { |
| 883 | + global $wgDBtype; |
| 884 | + if ($wgDBtype=='postgres') { // PostgreSQL: no memory tables, use RULE to emulate INSERT IGNORE |
| 885 | + return "CREATE OR REPLACE FUNCTION create_" . $tablename . "() RETURNS void AS " |
| 886 | + ."$$ " |
| 887 | + ."BEGIN " |
| 888 | + ." IF EXISTS(SELECT NULL FROM pg_tables WHERE tablename='" . $tablename . "' AND schemaname = ANY (current_schemas(true))) " |
| 889 | + ." THEN DELETE FROM " . $tablename ."; " |
| 890 | + ." ELSE " |
| 891 | + ." CREATE TEMPORARY TABLE " . $tablename . " (id INTEGER PRIMARY KEY); " |
| 892 | + ." CREATE RULE " . $tablename . "_ignore AS ON INSERT TO " . $tablename . " WHERE (EXISTS (SELECT NULL FROM " . $tablename |
| 893 | + ." WHERE (" . $tablename . ".id = new.id))) DO INSTEAD NOTHING; " |
| 894 | + ." END IF; " |
| 895 | + ."END; " |
| 896 | + ."$$ " |
| 897 | + ."LANGUAGE 'plpgsql'; " |
| 898 | + ."SELECT create_" . $tablename . "(); "; |
| 899 | + } else { // MySQL_ just a temporary table, use INSERT IGNORE later |
| 900 | + return "CREATE TEMPORARY TABLE " . $tablename . "( id INT UNSIGNED KEY ) TYPE=MEMORY"; |
| 901 | + } |
| 902 | + } |
| 903 | + |
862 | 904 | } |