Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2.php |
— | — | @@ -14,20 +14,28 @@ |
15 | 15 | define('SMW_SQL2_SMWPREDEFIW',':smw-preprop'); // virtual "interwiki prefix" marking predefined objects (non-movable) |
16 | 16 | define('SMW_SQL2_SMWINTDEFIW',':smw-intprop'); // virtual "interwiki prefix" marking internal (invisible) predefined properties |
17 | 17 | |
18 | | -// Constant flags for identifying tables/retrieval types: |
19 | | -define('SMW_SQL2_NONE',0); |
20 | | -define('SMW_SQL2_RELS2',1); |
21 | | -define('SMW_SQL2_ATTS2',2); |
22 | | -define('SMW_SQL2_TEXT2',4); |
23 | | -define('SMW_SQL2_SPEC2',8); |
24 | | -define('SMW_SQL2_REDI2',16); |
25 | | -define('SMW_SQL2_NARY2',32); // not really a table, but a retrieval type |
26 | | -define('SMW_SQL2_SUBS2',64); // subcategory table (formerly subcategories+subproperties) |
27 | | -define('SMW_SQL2_INST2',128); |
28 | | -define('SMW_SQL2_CONC2',256); |
29 | | -define('SMW_SQL2_SUBP2',512); // subproperty table |
| 18 | +/// @todo Document. |
| 19 | +class SMWSQLStore2Table { |
| 20 | + public $name; |
| 21 | + public $objectfields; |
| 22 | + public $fixedproperty; |
| 23 | + public $indexes; |
| 24 | + public $idsubject = true; |
| 25 | + public $specpropsonly = false; |
30 | 26 | |
| 27 | + public function __construct($name, $objectfields, $indexes = array(), $fixedproperty = false) { |
| 28 | + $this->name = $name; |
| 29 | + $this->objectfields = $objectfields; |
| 30 | + $this->fixedproperty = $fixedproperty; |
| 31 | + $this->indexes = $indexes; |
| 32 | + } |
31 | 33 | |
| 34 | + public function getFieldSignature() { |
| 35 | + return implode($this->objectfields,''); |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | + |
32 | 40 | /** |
33 | 41 | * Storage access class for using the standard MediaWiki SQL database |
34 | 42 | * for keeping semantic data. |
— | — | @@ -53,6 +61,9 @@ |
54 | 62 | /// >0 while getSemanticData runs, used to prevent nested calls from clearing the cache while another call runs and is about to fill it with data |
55 | 63 | protected static $in_getSemanticData = 0; |
56 | 64 | |
| 65 | + private static $prop_tables = array(); |
| 66 | + private static $fixed_prop_tables = null; // used as a cache "propkey => propid" but built only when needed |
| 67 | + |
57 | 68 | /// Use pre-defined ids for Very Important Properties, avoiding frequent ID lookups for those |
58 | 69 | private static $special_ids = array( |
59 | 70 | '_TYPE' => 1, |
— | — | @@ -69,54 +80,90 @@ |
70 | 81 | '_CONC' => 19, |
71 | 82 | '_SF_DF' => 20, // Semantic Form's default form property |
72 | 83 | '_SF_AF' => 21, // Semantic Form's alternate form property |
73 | | - '_ERRP' => 22 |
| 84 | + '_ERRP' => 22, |
| 85 | + '_1' => 23, // properties for encoding (short) lists |
| 86 | + '_2' => 24, |
| 87 | + '_3' => 25, |
| 88 | + '_4' => 26, |
| 89 | + '_5' => 27, |
| 90 | + '_LIST' => 28, |
74 | 91 | ); |
75 | 92 | |
76 | | - /// This array defines how various datatypes should be handled internally. This |
77 | | - /// list usually agrees with the datatype constants given in SMWDataValueFactory, |
78 | | - /// but need not be complete: the default storage method is SMW_SQL2_ATTS2. |
79 | | - /// Note that some storage methods require datavalue objects to support specific |
80 | | - /// APIs, so arbitrary changes in this table may not work unless the according |
81 | | - /// datavalue class in SMWDataValueFactory supports those. |
82 | | - private static $storage_mode = array( |
83 | | - '_txt' => SMW_SQL2_TEXT2, // Text type |
84 | | - '_cod' => SMW_SQL2_TEXT2, // Code type |
85 | | - '_str' => SMW_SQL2_ATTS2, // String type |
86 | | - '_ema' => SMW_SQL2_ATTS2, // Email type |
87 | | - '_uri' => SMW_SQL2_ATTS2, // URL/URI type |
88 | | - '_anu' => SMW_SQL2_ATTS2, // Annotation URI type |
89 | | - '_wpg' => SMW_SQL2_RELS2, // Page type |
90 | | - '_wpp' => SMW_SQL2_RELS2, // Property page type |
91 | | - '_wpc' => SMW_SQL2_RELS2, // Category page type |
92 | | - '_wpf' => SMW_SQL2_RELS2, // Form page type (for Semantic Forms) |
93 | | - '_num' => SMW_SQL2_ATTS2, // Number type |
94 | | - '_tem' => SMW_SQL2_ATTS2, // Temperature type |
95 | | - '_dat' => SMW_SQL2_ATTS2, // Time type |
96 | | - '_geo' => SMW_SQL2_ATTS2, // Geographic coordinates type |
97 | | - '_boo' => SMW_SQL2_ATTS2, // Boolean type |
| 93 | + ///@todo Decide what to do with the forms type: it cannot have a page sig *and* live in the special table. |
| 94 | + private static $property_table_ids = array( |
| 95 | + '_txt' => 'smw_text2', // Text type |
| 96 | + '_cod' => 'smw_text2', // Code type |
| 97 | + '_str' => 'smw_atts2', // String type |
| 98 | + '_ema' => 'smw_atts2', // Email type |
| 99 | + '_uri' => 'smw_atts2', // URL/URI type |
| 100 | + '_anu' => 'smw_atts2', // Annotation URI type |
| 101 | + '_wpg' => 'smw_rels2', // Page type |
| 102 | + '_wpp' => 'smw_rels2', // Property page type |
| 103 | + '_wpc' => 'smw_rels2', // Category page type |
| 104 | + '_wpf' => 'smw_rels2', // Form page type (for Semantic Forms) |
| 105 | + '_num' => 'smw_atts2', // Number type |
| 106 | + '_tem' => 'smw_atts2', // Temperature type |
| 107 | + '_dat' => 'smw_atts2', // Time type |
| 108 | + '_geo' => 'smw_atts2', // Geographic coordinates type |
| 109 | + '_boo' => 'smw_atts2', // Boolean type |
| 110 | + '_lst' => 'smw_rels2', // Value list type (internal object) |
98 | 111 | // Special types are not avaialble directly for users (and have no local language name): |
99 | | - '__typ' => SMW_SQL2_SPEC2, // Special type page type |
100 | | - '__con' => SMW_SQL2_CONC2, // Special concept page type |
101 | | - '__sps' => SMW_SQL2_SPEC2, // Special string type |
102 | | - '__spu' => SMW_SQL2_SPEC2, // Special uri type |
103 | | - '__sup' => SMW_SQL2_SUBP2, // Special subproperty type |
104 | | - '__suc' => SMW_SQL2_SUBS2, // Special subcategory type |
105 | | - '__spf' => SMW_SQL2_SPEC2, // Special form type (for Semantic Forms) |
106 | | - '__sin' => SMW_SQL2_INST2, // Special instance of type |
107 | | - '__red' => SMW_SQL2_REDI2, // Special redirect type |
108 | | - '__lin' => SMW_SQL2_SPEC2, // Special linear unit conversion type |
109 | | - '__nry' => SMW_SQL2_NARY2, // Special multi-valued type |
110 | | - '__err' => SMW_SQL2_NONE, // Special error type, actually this is not stored right now |
111 | | - '__imp' => SMW_SQL2_SPEC2, // Special import vocabulary type |
112 | | - '__pro' => SMW_SQL2_NONE, // Property page type; actually this should never be stored as a value (_wpp is used there) |
| 112 | + '__typ' => 'smw_spec2', // Special type page type |
| 113 | +// '__con' => 'smw_conc2', // Special concept page type |
| 114 | + '__sps' => 'smw_spec2', // Special string type |
| 115 | + '__spu' => 'smw_spec2', // Special uri type |
| 116 | + '__sup' => 'smw_subp2', // Special subproperty type |
| 117 | + '__suc' => 'smw_subs2', // Special subcategory type |
| 118 | + '__spf' => 'smw_spec2', // Special form type (for Semantic Forms) |
| 119 | + '__sin' => 'smw_inst2', // Special instance of type |
| 120 | + '__red' => 'smw_redi2', // Special redirect type |
| 121 | + '__lin' => 'smw_spec2', // Special linear unit conversion type |
| 122 | + '__imp' => 'smw_spec2', // Special import vocabulary type |
| 123 | + '__err' => '', // Special error type, used to indicate that the table could not be determined (happens for type-polymorphic _1, _2, ...) |
| 124 | + //'__pro' => SMW_SQL2_NONE, // Property page type; actually this should never be stored as a value (_wpp is used there) |
113 | 125 | ); |
114 | 126 | |
| 127 | + ///@todo Document. |
| 128 | + ///@todo Make sure that respective DV objects agree with this cache. |
| 129 | + private static $type_signatures = array( |
| 130 | + '_txt' => array('l',-1,-1), // Text type |
| 131 | + '_cod' => array('l',-1,-1), // Code type |
| 132 | + '_str' => array('t',0,0), // String type |
| 133 | + '_ema' => array('t',0,0), // Email type |
| 134 | + '_uri' => array('t',0,0), // URL/URI type |
| 135 | + '_anu' => array('t',0,0), // Annotation URI type |
| 136 | + '_wpg' => array('tnwt',3,3), // Page type |
| 137 | + '_wpp' => array('tnwt',3,3), // Property page type |
| 138 | + '_wpc' => array('tnwt',3,3), // Category page type |
| 139 | + '_wpf' => array('tnwt',3,3), // Form page type (for Semantic Forms) |
| 140 | + '_num' => array('tfu',1,0), // Number type |
| 141 | + '_tem' => array('tfu',1,0), // Temperature type |
| 142 | + '_dat' => array('tf',1,0), // Time type |
| 143 | + '_geo' => array('t',0,0), // Geographic coordinates type |
| 144 | + '_boo' => array('t',0,0), // Boolean type |
| 145 | + '_lst' => array('tnwt',0,-1),// Value list type (internal object) |
| 146 | + // Special types are not avaialble directly for users (and have no local language name): |
| 147 | + '__typ' => array('t',0,0), // Special type page type |
| 148 | +// '__con' => 'smw_conc2', // Special concept page type |
| 149 | + '__sps' => array('t',0,0), // Special string type |
| 150 | + '__spu' => array('t',0,0), // Special uri type |
| 151 | + '__sup' => array('tnwt',3,3), // Special subproperty type |
| 152 | + '__suc' => array('tnwt',3,3), // Special subcategory type |
| 153 | + '__spf' => array('tnwt',3,3), // Special form type (for Semantic Forms) |
| 154 | + '__sin' => array('tnwt',3,3), // Special instance of type |
| 155 | + '__red' => array('tnwt',3,3), // Special redirect type |
| 156 | + '__lin' => array('tfu',1,0), // Special linear unit conversion type |
| 157 | + '__imp' => array('t',0,0), // Special import vocabulary type |
| 158 | +// '__err' => '', // Special error type, used to indicate that the table could not be determined (happens for type-polymorphic _1, _2, ...) |
| 159 | + '__pro' => array('t',0,0), // Property page type; never be stored as a value (_wpp is used there) but needed for sorting |
| 160 | + ); |
| 161 | + |
115 | 162 | ///// Reading methods ///// |
116 | 163 | |
117 | | - function getSemanticData($subject, $filter = false) { |
| 164 | + public function getSemanticData($subject, $filter = false) { |
118 | 165 | wfProfileIn("SMWSQLStore2::getSemanticData (SMW)"); |
119 | | - SMWSQLStore2::$in_getSemanticData++; |
120 | | - |
| 166 | + SMWSQLStore2::$in_getSemanticData++; // do not clear the cache when called recursively |
| 167 | + //*** Find out if this subject exists ***// |
121 | 168 | if ( $subject instanceof Title ) { ///TODO: can this still occur? |
122 | 169 | $sid = $this->getSMWPageID($subject->getDBkey(),$subject->getNamespace(),$subject->getInterwiki()); |
123 | 170 | $svalue = SMWWikiPageValue::makePageFromTitle($subject); |
— | — | @@ -134,37 +181,11 @@ |
135 | 182 | wfProfileOut("SMWSQLStore2::getSemanticData (SMW)"); |
136 | 183 | return isset($svalue)?(new SMWSemanticData($svalue)):null; |
137 | 184 | } |
138 | | - |
139 | | - if ($filter !== false) { //array as described in docu for SMWStore |
140 | | - $tasks = 0; |
141 | | - foreach ($filter as $value) { |
142 | | - $tasks = $tasks | SMWSQLStore2::getStorageMode($value); |
143 | | - } |
144 | | - } else { |
145 | | - $tasks = SMW_SQL2_RELS2 | SMW_SQL2_ATTS2 | SMW_SQL2_TEXT2| SMW_SQL2_SPEC2 | SMW_SQL2_NARY2 | SMW_SQL2_SUBS2 | SMW_SQL2_SUBP2 | SMW_SQL2_INST2 | SMW_SQL2_REDI2 | SMW_SQL2_CONC2; |
146 | | - } |
147 | | - if ($subject->getNamespace() != NS_CATEGORY) { |
148 | | - $tasks = $tasks & ~SMW_SQL2_SUBS2; |
149 | | - } |
150 | | - if ($subject->getNamespace() != SMW_NS_PROPERTY) { |
151 | | - $tasks = $tasks & ~SMW_SQL2_SUBP2; |
152 | | - } |
153 | | - if ($subject->getNamespace() != SMW_NS_CONCEPT) { |
154 | | - $tasks = $tasks & ~SMW_SQL2_CONC2; |
155 | | - } |
156 | | - |
| 185 | + //*** Prepare the cache ***// |
157 | 186 | if (!array_key_exists($sid, $this->m_semdata)) { // new cache entry |
158 | 187 | $this->m_semdata[$sid] = new SMWSemanticData($svalue, false); |
159 | | - $this->m_sdstate[$sid] = $tasks; |
160 | | - } else { // do only remaining tasks |
161 | | - $newtasks = $tasks & ~$this->m_sdstate[$sid]; |
162 | | - $this->m_sdstate[$sid] = $this->m_sdstate[$sid] | $tasks; |
163 | | - $tasks = $newtasks; |
| 188 | + $this->m_sdstate[$sid] = array(); |
164 | 189 | } |
165 | | - |
166 | | - if ($tasks != 0) { // fetch DB handler only when really needed! |
167 | | - $db =& wfGetDB( DB_SLAVE ); |
168 | | - } |
169 | 190 | if ( (count($this->m_semdata) > 20) && (SMWSQLStore2::$in_getSemanticData == 1) ) { |
170 | 191 | // prevent memory leak; |
171 | 192 | // It is not so easy to find the sweet spot between cache size and performance gains (both memory and time), |
— | — | @@ -172,170 +193,21 @@ |
173 | 194 | $this->m_semdata = array($sid => $this->m_semdata[$sid]); |
174 | 195 | $this->m_sdstate = array($sid => $this->m_sdstate[$sid]); |
175 | 196 | } |
176 | | - |
177 | | - // most types of data suggest rather similar code |
178 | | - foreach (array(SMW_SQL2_RELS2, SMW_SQL2_ATTS2, SMW_SQL2_TEXT2, SMW_SQL2_INST2, SMW_SQL2_SUBS2, SMW_SQL2_SUBP2, SMW_SQL2_SPEC2, SMW_SQL2_REDI2, SMW_SQL2_CONC2) as $task) { |
179 | | - if ( !($tasks & $task) ) continue; |
180 | | - wfProfileIn("SMWSQLStore2::getSemanticData-task$task (SMW)"); |
181 | | - $where = 'p_id=smw_id AND s_id=' . $db->addQuotes($sid); |
182 | | - switch ($task) { |
183 | | - case SMW_SQL2_RELS2: |
184 | | - $from = $db->tableName('smw_rels2') . ' INNER JOIN ' . $db->tableName('smw_ids') . ' AS p ON p_id=p.smw_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS o ON o_id=o.smw_id'; |
185 | | - $select = 'p.smw_title as prop, o.smw_title as title, o.smw_namespace as namespace, o.smw_iw as iw'; |
186 | | - $where = 's_id=' . $db->addQuotes($sid); |
187 | | - break; |
188 | | - case SMW_SQL2_ATTS2: |
189 | | - $from = array('smw_atts2','smw_ids'); |
190 | | - $select = 'smw_title as prop, value_unit as unit, value_xsd as value'; |
191 | | - break; |
192 | | - case SMW_SQL2_TEXT2: |
193 | | - $from = array('smw_text2','smw_ids'); |
194 | | - $select = 'smw_title as prop, value_blob as value'; |
195 | | - break; |
196 | | - case SMW_SQL2_SPEC2: |
197 | | - $from = 'smw_spec2'; |
198 | | - $select = 'p_id, value_string as value'; |
199 | | - $where = 's_id=' . $db->addQuotes($sid); |
200 | | - break; |
201 | | - case SMW_SQL2_SUBS2: |
202 | | - $from = array('smw_subs2','smw_ids'); |
203 | | - $select = 'smw_title as value'; |
204 | | - $where = 'o_id=smw_id AND s_id=' . $db->addQuotes($sid); |
205 | | - $namespace = NS_CATEGORY; |
206 | | - $specprop = '_SUBC'; |
207 | | - break; |
208 | | - case SMW_SQL2_SUBP2: |
209 | | - $from = array('smw_subp2','smw_ids'); |
210 | | - $select = 'smw_title as value'; |
211 | | - $where = 'o_id=smw_id AND s_id=' . $db->addQuotes($sid); |
212 | | - $namespace = SMW_NS_PROPERTY; |
213 | | - $specprop = '_SUBP'; |
214 | | - break; |
215 | | - case SMW_SQL2_REDI2: |
216 | | - $from = array('smw_redi2','smw_ids'); |
217 | | - $select = 'smw_title as title, smw_namespace as namespace'; |
218 | | - $where = 'o_id=smw_id AND s_title=' . $db->addQuotes($subject->getDBkey()) . |
219 | | - ' AND s_namespace=' . $db->addQuotes($subject->getNamespace()); |
220 | | - break; |
221 | | - case SMW_SQL2_INST2: |
222 | | - $from = array('smw_inst2','smw_ids'); |
223 | | - $select = 'smw_title as value'; |
224 | | - $where = 'o_id=smw_id AND s_id=' . $db->addQuotes($sid); |
225 | | - $namespace = NS_CATEGORY; |
226 | | - $specprop = '_INST'; |
227 | | - break; |
228 | | - case SMW_SQL2_CONC2: |
229 | | - $from = 'smw_conc2'; |
230 | | - $select = 'concept_txt as concept, concept_docu as docu, concept_features as features, concept_size as size, concept_depth as depth'; |
231 | | - $where = 's_id=' . $db->addQuotes($sid); |
232 | | - break; |
233 | | - } |
234 | | - $res = $db->select( $from, $select, $where, 'SMW::getSemanticData' ); |
235 | | - while($row = $db->fetchObject($res)) { |
236 | | - $valuekeys = false; |
237 | | - if ($task & (SMW_SQL2_RELS2 | SMW_SQL2_ATTS2 | SMW_SQL2_TEXT2) ) { |
238 | | - $propertyname = $row->prop; |
| 197 | + //*** Read the data ***// |
| 198 | + foreach (SMWSQLStore2::getPropertyTables() as $tid => $proptable) { |
| 199 | + if (array_key_exists($tid,$this->m_sdstate[$sid])) continue; |
| 200 | + if ($filter !== false) { |
| 201 | + $relevant = false; |
| 202 | + foreach ($filter as $typeid) { |
| 203 | + $relevant = $relevant || SMWSQLStore2::tableFitsType($tid,$typeid); |
239 | 204 | } |
240 | | - // The following cases are very similar, yet different in certain details: |
241 | | - if ($task == SMW_SQL2_RELS2) { |
242 | | - if ( ($row->iw === '') || ($row->iw{0} != ':') ) { // filter "special" iws that mark internal objects |
243 | | - $valuekeys = array($row->title, $row->namespace,$row->iw,''); |
244 | | - } |
245 | | - } elseif ($task == SMW_SQL2_ATTS2) { |
246 | | - $valuekeys = array($row->value, $row->unit); |
247 | | - } elseif ($task == SMW_SQL2_TEXT2) { |
248 | | - $valuekeys = array($row->value); |
249 | | - } elseif ($task == SMW_SQL2_SPEC2) { |
250 | | - $pid = array_search($row->p_id, SMWSQLStore2::$special_ids); |
251 | | - if ($pid != false) { |
252 | | - $propertyname = $pid; |
253 | | - } else { // this should be rare (only if some extension uses properties of "special" types) |
254 | | - $proprow = $db->selectRow('smw_ids', array('smw_title'), array('smw_id' => $row->p_id), 'SMW::getSemanticData'); |
255 | | - /// TODO: $proprow may be false (inconsistent DB but anyway); maybe check and be gentle in some way |
256 | | - $propertyname = $proprow->smw_title; |
257 | | - } |
258 | | - $valuekeys = array($row->value); |
259 | | - } elseif ( ($task == SMW_SQL2_SUBS2) || ($task == SMW_SQL2_SUBP2) || ($task == SMW_SQL2_INST2) ) { |
260 | | - $propertyname = $specprop; |
261 | | - $valuekeys = array($row->value,$namespace,'',''); |
262 | | - } elseif ($task == SMW_SQL2_REDI2) { |
263 | | - $propertyname = '_REDI'; |
264 | | - $valuekeys = array($row->title, $row->namespace,'',''); |
265 | | - } elseif ($task == SMW_SQL2_CONC2) { |
266 | | - $propertyname = '_CONC'; |
267 | | - $valuekeys = array($row->concept, $row->docu, $row->features, $row->size, $row->depth); |
268 | | - } |
269 | | - if ($valuekeys !== false) { |
270 | | - $this->m_semdata[$sid]->addPropertyStubValue($propertyname, $valuekeys); |
271 | | - } |
| 205 | + if (!$relevant) continue; |
272 | 206 | } |
273 | | - $db->freeResult($res); |
274 | | - wfProfileOut("SMWSQLStore2::getSemanticData-task$task (SMW)"); |
275 | | - } |
276 | | - |
277 | | - // nary values |
278 | | - if ($tasks & SMW_SQL2_NARY2) { |
279 | | - // here we fetch all relevant data at once, with one call per table |
280 | | - // requires filling out data for all properties in parallel |
281 | | - $properties = array(); // property title objects indexed by DBkey |
282 | | - $ptypes = array(); // arrays of subtypes per property, indexed by DBkey |
283 | | - $dvs = array(); // datavalue objects, nested array: property DBkey x bnode x Pos |
284 | | - |
285 | | - foreach (array('smw_rels2','smw_atts2','smw_text2') as $table) { |
286 | | - switch ($table) { |
287 | | - case 'smw_rels2': |
288 | | - $sql='SELECT r.o_id AS bnode, prop.smw_title AS prop, pos.smw_title AS pos, o.smw_title AS title, o.smw_namespace AS namespace, o.smw_iw AS iw FROM ' . $db->tableName('smw_rels2') . ' AS r INNER JOIN ' . $db->tableName('smw_rels2') . ' AS r2 ON r.o_id=r2.s_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS pos ON pos.smw_id=r2.p_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS prop ON prop.smw_id=r.p_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS o ON o.smw_id=r2.o_id WHERE pos.smw_iw=' . $db->addQuotes(SMW_SQL2_SMWIW) . ' AND r.s_id=' . $db->addQuotes($sid); |
289 | | - break; |
290 | | - case 'smw_atts2': |
291 | | - $sql='SELECT r.o_id AS bnode, prop.smw_title AS prop, pos.smw_title AS pos, att.value_unit AS unit, att.value_xsd AS xsd FROM ' . $db->tableName('smw_rels2') . ' AS r INNER JOIN ' . $db->tableName('smw_atts2') . ' AS att ON r.o_id=att.s_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS pos ON pos.smw_id=att.p_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS prop ON prop.smw_id=r.p_id WHERE pos.smw_iw=' . $db->addQuotes(SMW_SQL2_SMWIW) . ' AND r.s_id=' . $db->addQuotes($sid); |
292 | | - break; |
293 | | - case 'smw_text2': |
294 | | - $sql='SELECT r.o_id AS bnode, prop.smw_title AS prop, pos.smw_title AS pos, text.value_blob AS xsd FROM ' . $db->tableName('smw_rels2') . ' AS r INNER JOIN ' . $db->tableName('smw_text2') . ' AS text ON r.o_id=text.s_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS pos ON pos.smw_id=text.p_id INNER JOIN ' . $db->tableName('smw_ids') . ' AS prop ON prop.smw_id=r.p_id WHERE pos.smw_iw=' . $db->addQuotes(SMW_SQL2_SMWIW) . ' AND r.s_id=' . $db->addQuotes($sid); |
295 | | - break; |
296 | | - } |
297 | | - $res = $db->query($sql, 'SMWSQLStore2::getSemanticData-nary'); |
298 | | - while($row = $db->fetchObject($res)) { |
299 | | - if ( !array_key_exists($row->prop,$properties) ) { |
300 | | - $properties[$row->prop] = SMWPropertyValue::makeUserProperty($row->prop); |
301 | | - $type = $properties[$row->prop]->getTypesValue(); |
302 | | - $ptypes[$row->prop] = $type->getTypeValues(); |
303 | | - $dvs[$row->prop] = array(); |
304 | | - } |
305 | | - $pos = intval($row->pos); |
306 | | - if ($pos >= count($ptypes[$row->prop])) continue; // out of range, maybe some old data that still waits for update |
307 | | - if (!array_key_exists($row->bnode,$dvs[$row->prop])) { |
308 | | - $dvs[$row->prop][$row->bnode] = array(); |
309 | | - for ($i=0; $i < count($ptypes[$row->prop]); $i++) { // init array |
310 | | - $dvs[$row->prop][$row->bnode][$i] = null; |
311 | | - } |
312 | | - } |
313 | | - $dv = SMWDataValueFactory::newTypeObjectValue($ptypes[$row->prop][$pos]); |
314 | | - switch ($table) { |
315 | | - case 'smw_rels2': |
316 | | - $dv->setDBkeys(array($row->title, $row->namespace)); |
317 | | - break; |
318 | | - case 'smw_atts2': |
319 | | - $dv->setDBkeys(array($row->xsd, $row->unit)); |
320 | | - break; |
321 | | - case 'smw_text2': |
322 | | - $dv->setDBkeys(array($row->xsd)); |
323 | | - break; |
324 | | - } |
325 | | - $dvs[$row->prop][$row->bnode][$pos] = $dv; |
326 | | - } |
327 | | - $db->freeResult($res); |
| 207 | + $data = $this->fetchSemanticData($sid,$svalue,$proptable); |
| 208 | + foreach ($data as $d) { |
| 209 | + $this->m_semdata[$sid]->addPropertyStubValue(reset($d),end($d)); |
328 | 210 | } |
329 | | - |
330 | | - foreach ($properties as $name => $property) { |
331 | | - $pdvs = $dvs[$name]; |
332 | | - foreach ($pdvs as $bnode => $values) { |
333 | | - $dv = SMWDataValueFactory::newPropertyObjectValue($property); |
334 | | - if ($dv instanceof SMWNAryValue) { |
335 | | - $dv->setDVs($values); |
336 | | - $this->m_semdata[$sid]->addPropertyObjectValue($property, $dv); |
337 | | - } |
338 | | - } |
339 | | - } |
| 211 | + $this->m_sdstate[$sid][$tid] = true; |
340 | 212 | } |
341 | 213 | |
342 | 214 | SMWSQLStore2::$in_getSemanticData--; |
— | — | @@ -343,18 +215,12 @@ |
344 | 216 | return $this->m_semdata[$sid]; |
345 | 217 | } |
346 | 218 | |
347 | | - /** |
348 | | - * @todo While the function can retrieve all values of a given property (i.e. values occurring for any subject), |
349 | | - * it currently will only do this for user-defined properties that are not multi-valued. |
350 | | - */ |
351 | | - function getPropertyValues($subject, SMWPropertyValue $property, $requestoptions = null, $outputformat = '') { |
| 219 | + public function getPropertyValues($subject, SMWPropertyValue $property, $requestoptions = null, $outputformat = '') { |
352 | 220 | wfProfileIn("SMWSQLStore2::getPropertyValues (SMW)"); |
353 | 221 | if ($property->isInverse()) { // inverses are working differently |
354 | 222 | $noninverse = clone $property; |
355 | 223 | $noninverse->setInverse(false); |
356 | 224 | $result = $this->getPropertySubjects($noninverse,$subject,$requestoptions); |
357 | | - wfProfileOut("SMWSQLStore2::getPropertyValues (SMW)"); |
358 | | - return $result; |
359 | 225 | } elseif ($subject !== null) { // subject given, use semantic data cache: |
360 | 226 | $sd = $this->getSemanticData($subject,array($property->getPropertyTypeID())); |
361 | 227 | $result = $this->applyRequestOptions($sd->getPropertyValues($property),$requestoptions); |
— | — | @@ -369,129 +235,153 @@ |
370 | 236 | } |
371 | 237 | } else { // no subject given, get all values for the given property |
372 | 238 | $pid = $this->getSMWPropertyID($property); |
373 | | - if ( $pid == 0 ) { |
| 239 | + $tableid = SMWSQLStore2::findPropertyTableID($property); |
| 240 | + if ( ($pid == 0) || ($tableid == '') ) { |
374 | 241 | wfProfileOut("SMWSQLStore2::getPropertyValues (SMW)"); |
375 | 242 | return array(); |
376 | 243 | } |
377 | | - $db =& wfGetDB( DB_SLAVE ); |
| 244 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 245 | + $data = $this->fetchSemanticData($pid,$property,$proptables[$tableid],false,$requestoptions); |
378 | 246 | $result = array(); |
379 | | - $mode = SMWSQLStore2::getStorageMode($property->getPropertyTypeID()); |
380 | | - switch ($mode) { |
381 | | - case SMW_SQL2_TEXT2: |
382 | | - $res = $db->select( 'smw_text2', 'value_blob', |
383 | | - 'p_id=' . $db->addQuotes($pid), |
384 | | - 'SMW::getPropertyValues', $this->getSQLOptions($requestoptions) ); ///NOTE: Do not add DISTINCT here for performance reasons |
385 | | - while($row = $db->fetchObject($res)) { |
386 | | - $dv = SMWDataValueFactory::newPropertyObjectValue($property); |
387 | | - $dv->setOutputFormat($outputformat); |
388 | | - $dv->setDBkeys(array($row->value_blob)); |
389 | | - $result[] = $dv; |
390 | | - } |
391 | | - $db->freeResult($res); |
392 | | - break; |
393 | | - case SMW_SQL2_RELS2: |
394 | | - $res = $db->select( array('smw_rels2', 'smw_ids'), |
395 | | - 'smw_namespace, smw_title, smw_iw', |
396 | | - 'p_id=' . $db->addQuotes($pid) . ' AND o_id=smw_id' . |
397 | | - $this->getSQLConditions($requestoptions,'smw_sortkey','smw_sortkey'), |
398 | | - 'SMW::getPropertyValues', $this->getSQLOptions($requestoptions,'smw_sortkey') + array('DISTINCT') ); |
399 | | - while($row = $db->fetchObject($res)) { |
400 | | - $dv = SMWDataValueFactory::newPropertyObjectValue($property); |
401 | | - $dv->setOutputFormat($outputformat); |
402 | | - $dv->setDBkeys(array($row->smw_title, $row->smw_namespace, $row->smw_iw)); |
403 | | - $result[] = $dv; |
404 | | - } |
405 | | - $db->freeResult($res); |
406 | | - break; |
407 | | - case SMW_SQL2_ATTS2: |
408 | | - if ( ($requestoptions !== null) && ($requestoptions->boundary !== null) ) { // the quick way to find out if this is a numeric type |
409 | | - $value_column = $requestoptions->boundary->isNumeric()?'value_num':'value_xsd'; |
410 | | - } else { // need to do more work to find out if this is a numeric type |
411 | | - $testval = SMWDataValueFactory::newTypeIDValue($property->getPropertyTypeID()); |
412 | | - $value_column = $testval->isNumeric()?'value_num':'value_xsd'; |
413 | | - } |
414 | | - $sql = 'p_id=' . $db->addQuotes($pid) . |
415 | | - $this->getSQLConditions($requestoptions,$value_column,'value_xsd'); |
416 | | - $res = $db->select( 'smw_atts2', 'value_unit, value_xsd', |
417 | | - 'p_id=' . $db->addQuotes($pid) . |
418 | | - $this->getSQLConditions($requestoptions,$value_column,'value_xsd'), |
419 | | - 'SMW::getPropertyValues', $this->getSQLOptions($requestoptions,$value_column) + array('DISTINCT') ); |
420 | | - while($row = $db->fetchObject($res)) { |
421 | | - $dv = SMWDataValueFactory::newPropertyObjectValue($property); |
422 | | - $dv->setOutputFormat($outputformat); |
423 | | - $dv->setDBkeys(array($row->value_xsd, $row->value_unit)); |
424 | | - $result[] = $dv; |
425 | | - } |
426 | | - $db->freeResult($res); |
427 | | - break; |
428 | | - case SMW_SQL2_NARY2: ///TODO: currently disabled |
429 | | -// $type = $property->getTypesValue(); |
430 | | -// $subtypes = $type->getTypeValues(); |
431 | | -// $res = $db->select( $db->tableName('smw_nary'), |
432 | | -// 'nary_key', |
433 | | -// $subjectcond . |
434 | | -// 'attribute_title=' . $db->addQuotes($property->getDBkey()), |
435 | | -// 'SMW::getPropertyValues', $this->getSQLOptions($requestoptions) ); |
436 | | -// ///TODO: presumably slow. Try to do less SQL queries by making a join with smw_nary |
437 | | -// while($row = $db->fetchObject($res)) { |
438 | | -// $values = array(); |
439 | | -// for ($i=0; $i < count($subtypes); $i++) { // init array |
440 | | -// $values[$i] = null; |
441 | | -// } |
442 | | -// $res2 = $db->select( $db->tableName('smw_nary_attributes'), |
443 | | -// 'nary_pos, value_unit, value_xsd', |
444 | | -// $subjectcond . |
445 | | -// 'nary_key=' . $db->addQuotes($row->nary_key), |
446 | | -// 'SMW::getPropertyValues'); |
447 | | -// while($row2 = $db->fetchObject($res2)) { |
448 | | -// if ($row2->nary_pos < count($subtypes)) { |
449 | | -// $dv = SMWDataValueFactory::newTypeObjectValue($subtypes[$row2->nary_pos]); |
450 | | -// $dv->setDBkeys(array($row2->value_xsd, $row2->value_unit)); |
451 | | -// $values[$row2->nary_pos] = $dv; |
452 | | -// } |
453 | | -// } |
454 | | -// $db->freeResult($res2); |
455 | | -// $res2 = $db->select( $db->tableName('smw_nary_longstrings'), |
456 | | -// 'nary_pos, value_blob', |
457 | | -// $subjectcond . |
458 | | -// 'nary_key=' . $db->addQuotes($row->nary_key), |
459 | | -// 'SMW::getPropertyValues'); |
460 | | -// while($row2 = $db->fetchObject($res2)) { |
461 | | -// if ( $row2->nary_pos < count($subtypes) ) { |
462 | | -// $dv = SMWDataValueFactory::newTypeObjectValue($subtypes[$row2->nary_pos]); |
463 | | -// $dv->setDBkeys(array($row2->value_blob)); |
464 | | -// $values[$row2->nary_pos] = $dv; |
465 | | -// } |
466 | | -// } |
467 | | -// $db->freeResult($res2); |
468 | | -// $res2 = $db->select( $db->tableName('smw_nary_relations'), |
469 | | -// 'nary_pos, object_title, object_namespace, object_id', |
470 | | -// $subjectcond . |
471 | | -// 'nary_key=' . $db->addQuotes($row->nary_key), |
472 | | -// 'SMW::getPropertyValues'); |
473 | | -// while($row2 = $db->fetchObject($res2)) { |
474 | | -// if ( ($row2->nary_pos < count($subtypes)) && |
475 | | -// ($subtypes[$row2->nary_pos]->getDBkey() == '_wpg') ) { |
476 | | -// $dv = SMWDataValueFactory::newTypeIDValue('_wpg'); |
477 | | -// $dv->setValues($row2->object_title, $row2->object_namespace, $row2->object_id); |
478 | | -// $values[$row2->nary_pos] = $dv; |
479 | | -// } |
480 | | -// } |
481 | | -// $db->freeResult($res2); |
482 | | -// $dv = SMWDataValueFactory::newPropertyObjectValue($property); |
483 | | -// $dv->setOutputFormat($outputformat); |
484 | | -// $dv->setDVs($values); |
485 | | -// $result[] = $dv; |
486 | | -// } |
487 | | -// $db->freeResult($res); |
488 | | - break; |
| 247 | + foreach ($data as $dbkeys) { |
| 248 | + $dv = SMWDataValueFactory::newPropertyObjectValue($property); |
| 249 | + if ($outputformat != '') $dv->setOutputFormat($outputformat); |
| 250 | + $dv->setDBkeys($dbkeys); |
| 251 | + $result[] = $dv; |
489 | 252 | } |
490 | 253 | } |
491 | 254 | wfProfileOut("SMWSQLStore2::getPropertyValues (SMW)"); |
492 | 255 | return $result; |
493 | 256 | } |
494 | 257 | |
495 | | - function getPropertySubjects(SMWPropertyValue $property, $value, $requestoptions = null) { |
| 258 | + /** |
| 259 | + * Helper function for reading all data for from a given property table (specified by an |
| 260 | + * SMWSQLStore2Table object), based on certain restrictions. The function can filter data |
| 261 | + * based on the subject (1) or on the property it belongs to (2) -- but one of those must |
| 262 | + * be done. The Boolean $issubject is true for (1) and false for (2). |
| 263 | + * |
| 264 | + * In case (1), the first two parameters are taken to refer to a subject; in case (2) they |
| 265 | + * are taken to refer to a property. In any case, the retrieval is limited to the specified |
| 266 | + * $proptable. The parameters are an internal $id (of a subject or property), and an $object |
| 267 | + * (being an SMWPageValue or SMWPropertyValue). Moreover, when filtering by property, it is |
| 268 | + * assumed that the given $proptable belongs to the property: if it is a table with fixed |
| 269 | + * property, it will not be checked that this is the same property as the one that was given |
| 270 | + * in $object. |
| 271 | + * |
| 272 | + * In case (1), the result in general is an array of pairs (arrays of size 2) consisting of |
| 273 | + * a property name (string), and an array of DB keys (array) from which a datvalue object for |
| 274 | + * this value could be built. It is possible that some of the DB keys are based on internal |
| 275 | + * objects; these will be represented by similar result arrays of (recursive calls of) |
| 276 | + * fetchSemanticData(). |
| 277 | + * |
| 278 | + * In case (2), the result is simply an array of DB keys (array) without the property strings. |
| 279 | + * Container objects will be encoded with nested arrays like in case (1). |
| 280 | + * |
| 281 | + * @todo Maybe share DB handler; asking for it seems to take quite some time and we do not want |
| 282 | + * to change it in one call. |
| 283 | + */ |
| 284 | + protected function fetchSemanticData($id, $object, $proptable, $issubject=true, $requestoptions = null ) { |
| 285 | + // stop if there is not enough data: |
| 286 | + // properties always need to be given as object, subjects at least if !$proptable->idsubject |
| 287 | + if ( ($id == 0) || ( ($object === null) && (!$issubject || !$proptable->idsubject) ) ) return array(); |
| 288 | + wfProfileIn("SMWSQLStore2::fetchSemanticData-" . $proptable->name . " (SMW)"); |
| 289 | + $result = array(); |
| 290 | + $db =& wfGetDB( DB_SLAVE ); |
| 291 | + |
| 292 | + //*** First build $from, $select, and $where for the DB query ***// |
| 293 | + $from = $db->tableName($proptable->name); // always use actual table |
| 294 | + $select = ''; |
| 295 | + $where = ''; |
| 296 | + if ($issubject != 0) { // restrict subject, select property |
| 297 | + $where .= ($proptable->idsubject)? 's_id=' . $db->addQuotes($id) : |
| 298 | + 's_title=' . $db->addQuotes($object->getDBkey()) . |
| 299 | + ' AND s_namespace=' . $db->addQuotes($object->getNamespace()); |
| 300 | + if (!$proptable->fixedproperty && !$proptable->specpropsonly) { // get property name |
| 301 | + $from .= ' INNER JOIN ' . $db->tableName('smw_ids') . ' AS p ON p_id=p.smw_id'; |
| 302 | + $select .= 'p.smw_title as prop'; |
| 303 | + } elseif ($proptable->specpropsonly) { // avoid join for tables that contain only built-in properties |
| 304 | + $select .= 'p_id'; |
| 305 | + } // else: fixed property, no select needed at all to get at it |
| 306 | + } elseif (!$proptable->fixedproperty) { // restrict property, but don't select subject |
| 307 | + $where .= 'p_id=' . $db->addQuotes($id); |
| 308 | + } |
| 309 | + $valuecount = 0; |
| 310 | + $pagevalues = array(); // collect indices of page-type components of this table (typically at most 1) |
| 311 | + $usedistinct = true; // use DISTINCT option only if no text blobs are among values |
| 312 | + $selectvalues = array(); //array for all values to be selected, kept to help finding value and label fields below |
| 313 | + foreach ($proptable->objectfields as $fieldname => $typeid) { // now add select entries for object column(s) |
| 314 | + if ($typeid == 'p') { // Special case: page id, use smw_id table to insert 4 page-specific values instead of internal id |
| 315 | + $from .= ' INNER JOIN ' . $db->tableName('smw_ids') . " AS o$valuecount ON $fieldname=o$valuecount.smw_id"; |
| 316 | + $select .= (($select!='')?',':'') . "$fieldname AS id$valuecount"; |
| 317 | + $selectvalues[$valuecount] = "o$valuecount.smw_title"; |
| 318 | + $selectvalues[$valuecount+1] = "o$valuecount.smw_namespace"; |
| 319 | + $selectvalues[$valuecount+2] = "o$valuecount.smw_iw"; |
| 320 | + $selectvalues[$valuecount+3] = "o$valuecount.smw_sortkey"; |
| 321 | + $pagevalues[] = $valuecount; |
| 322 | + $valuecount += 3; |
| 323 | + } else { // just use value as given |
| 324 | + $selectvalues[$valuecount] = $fieldname; |
| 325 | + } |
| 326 | + if ($typeid == 'l') $usedistinct = false; |
| 327 | + $valuecount++; |
| 328 | + } |
| 329 | + foreach ($selectvalues as $index => $field) { |
| 330 | + $select .= (($select!='')?',':'') . "$field AS v$index"; |
| 331 | + } |
| 332 | + if (!$issubject) { // needed to apply sorting/string matching in query; only with fixed property |
| 333 | + list($sig,$valueindex,$labelindex) = SMWSQLStore2::getTypeSignature($object->getPropertyTypeID()); |
| 334 | + $valuecolumn = (array_key_exists($valueindex,$selectvalues))?$selectvalues[$valueindex]:''; |
| 335 | + $labelcolumn = (array_key_exists($labelindex,$selectvalues))?$selectvalues[$labelindex]:''; |
| 336 | + $where .= $this->getSQLConditions($requestoptions,$valuecolumn,$labelcolumn,$where!=''); |
| 337 | + } else { |
| 338 | + $valuecolumn = $labelcolumn = ''; |
| 339 | + } |
| 340 | + |
| 341 | + //*** Now execute the query and read the results ***// |
| 342 | + $res = $db->select( $from, $select, $where, 'SMW::getSemanticData', |
| 343 | + ($usedistinct?$this->getSQLOptions($requestoptions,$valuecolumn)+array('DISTINCT'): |
| 344 | + $this->getSQLOptions($requestoptions,$valuecolumn))); |
| 345 | + while($row = $db->fetchObject($res)) { |
| 346 | + if (!$issubject) { |
| 347 | + $propertyname = 'fixed'; // irrelevant, but use this to check if the data is good |
| 348 | + } elseif (!$proptable->fixedproperty) { // use joined or predefined property name |
| 349 | + $propertyname = ($proptable->specpropsonly) ? |
| 350 | + array_search($row->p_id, SMWSQLStore2::$special_ids) : $row->prop; |
| 351 | + } else { // use fixed property name |
| 352 | + $propertyname = $proptable->fixedproperty; |
| 353 | + } |
| 354 | + $valuekeys = array(); |
| 355 | + reset($pagevalues); |
| 356 | + for ($i=0; $i<$valuecount; $i++) { // read the value fields from the current row |
| 357 | + $fieldname = "v$i"; |
| 358 | + $newvalue = $row->$fieldname; |
| 359 | + if ($i == current($pagevalues)) { // special check for pages to filter out internal objects |
| 360 | + $iwfield = 'v' . ($i+2); |
| 361 | + $iw = $row->$iwfield; |
| 362 | + if ( ($iw == SMW_SQL2_SMWIW) && ($valuecount == 4) && ($object !== null) ) { |
| 363 | + // read container objects recursively; but only if proptable is of form "p" |
| 364 | + // also avoid (hypothetical) double recursion by requiring $object!==null |
| 365 | + $i += 3; // skip other page fields of this bnode |
| 366 | + $oidfield = 'id' . current($pagevalues); |
| 367 | + $newvalue = array(); |
| 368 | + foreach (SMWSQLStore2::getPropertyTables() as $tid => $pt) { // just read all |
| 369 | + $newvalue = array_merge($newvalue,$this->fetchSemanticData($row->$oidfield, null,$pt)); |
| 370 | + } |
| 371 | + } elseif ($iw{0} == ':') { // other internal object, maybe a DB inconsistency; ignore row |
| 372 | + $propertyname = ''; |
| 373 | + } |
| 374 | + next($pagevalues); |
| 375 | + } |
| 376 | + $valuekeys[] = $newvalue; |
| 377 | + } |
| 378 | + if ($propertyname != '') $result[] = $issubject?array($propertyname, $valuekeys):$valuekeys; |
| 379 | + } |
| 380 | + $db->freeResult($res); |
| 381 | + wfProfileOut("SMWSQLStore2::fetchSemanticData-" . $proptable->name . " (SMW)"); |
| 382 | + return $result; |
| 383 | + } |
| 384 | + |
| 385 | + public function getPropertySubjects(SMWPropertyValue $property, $value, $requestoptions = null) { |
496 | 386 | /// TODO: should we share code with #ask query computation here? Just use queries? |
497 | 387 | wfProfileIn("SMWSQLStore2::getPropertySubjects (SMW)"); |
498 | 388 | if ($property->isInverse()) { // inverses are working differently |
— | — | @@ -501,194 +391,199 @@ |
502 | 392 | wfProfileOut("SMWSQLStore2::getPropertySubjects (SMW)"); |
503 | 393 | return $result; |
504 | 394 | } |
505 | | - $result = array(); |
| 395 | + |
| 396 | + //*** First build $select, $from, and $where for the DB query ***// |
| 397 | + $select = $where = $from = ''; |
506 | 398 | $pid = $this->getSMWPropertyID($property); |
507 | | - if ( ($pid == 0) || ( ($value !== null) && (!$value->isValid()) ) ) { |
508 | | - wfProfileOut("SMWSQLStore2::getPropertySubjects (SMW)"); |
509 | | - return $result; |
| 399 | + $tableid = SMWSQLStore2::findPropertyTableID($property); |
| 400 | + if ( ($tableid == '') && ($value!==null) ) { // maybe a type-polymorphic property like _1; use value to find type |
| 401 | + $tableid = SMWSQLStore2::findTypeTableID($value->getTypeID()); |
510 | 402 | } |
| 403 | + if ( ($pid == 0) || ($tableid == '') || ( ($value !== null) && (!$value->isValid()) ) ) { |
| 404 | + return array(); |
| 405 | + } |
| 406 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 407 | + $proptable = $proptables[$tableid]; |
511 | 408 | $db =& wfGetDB( DB_SLAVE ); |
512 | | - // The following DB calls are all very similar, so we try to share a much code as possible. |
513 | | - // If the $table parameter is set, a standard query is used in the end. Only n-aries and |
514 | | - // redirects work differently. |
515 | | - $table = ''; |
516 | | - $sql = 'p_id=' . $db->addQuotes($pid); |
517 | | - $typeid = $property->getPropertyTypeID(); |
518 | | - $mode = SMWSQLStore2::getStorageMode($typeid); |
| 409 | + if ($proptable->idsubject) { // join in smw_ids to get title data |
| 410 | + $from = $db->tableName('smw_ids') . " INNER JOIN " . $db->tableName($proptable->name) . " AS t1 ON t1.s_id=smw_id"; |
| 411 | + $select = 'smw_title AS title, smw_namespace AS namespace, smw_sortkey'; |
| 412 | + } else { // no join needed, title+namespace as given in proptable |
| 413 | + $from = $db->tableName($proptable->name) . " AS t1"; |
| 414 | + $select = 's_title AS title, s_namespace AS namespace, s_title AS smw_sortkey'; |
| 415 | + } |
| 416 | + if ($proptable->fixedproperty == false) { |
| 417 | + $where .= ($where?' AND ':'') . "t1.p_id=" . $db->addQuotes($pid); |
| 418 | + } |
| 419 | + $this->prepareValueQuery($from,$where,$proptable,$value,1); |
519 | 420 | |
520 | | - switch ($mode) { |
521 | | - case SMW_SQL2_TEXT2: |
522 | | - $table = 'smw_text2'; // ignore value condition in this case |
523 | | - break; |
524 | | - case SMW_SQL2_CONC2: |
525 | | - $table = 'smw_conc2'; // ignore value condition in this case |
526 | | - break; |
527 | | - case SMW_SQL2_RELS2: case SMW_SQL2_INST2: case SMW_SQL2_SUBS2: case SMW_SQL2_SUBP2: |
528 | | - if ($mode!=SMW_SQL2_RELS2) $sql = ''; // no property column here |
529 | | -// if ($mode==SMW_SQL2_SUBS2) { // this table is shared, filter the relevant case |
530 | | -// $sql = 'smw_namespace=' . (($typeid == '__sup')?$db->addQuotes(SMW_NS_PROPERTY):$db->addQuotes(NS_CATEGORY)); |
531 | | -// } |
532 | | - if ($value !== null) { |
533 | | - $oid = $this->getSMWPageID($value->getDBkey(),$value->getNamespace(),$value->getInterwiki()); |
534 | | - $sql .= ($sql?" AND ":'') . 'o_id=' . $db->addQuotes($oid); |
535 | | - } |
536 | | - if ( ($value === null) || ($oid != 0) ) { |
537 | | - switch ($mode) { |
538 | | - case SMW_SQL2_RELS2: $table = 'smw_rels2'; break; |
539 | | - case SMW_SQL2_INST2: $table = 'smw_inst2'; break; |
540 | | - case SMW_SQL2_SUBS2: $table = 'smw_subs2'; break; |
541 | | - case SMW_SQL2_SUBP2: $table = 'smw_subp2'; break; |
| 421 | + //*** Now execute the query and read the results ***// |
| 422 | + $result = array(); |
| 423 | + $res = $db->select( $from, 'DISTINCT ' . $select, |
| 424 | + $where . $this->getSQLConditions($requestoptions,'smw_sortkey','smw_sortkey',$where!=''), |
| 425 | + 'SMW::getPropertySubjects', |
| 426 | + $this->getSQLOptions($requestoptions,'smw_sortkey') ); |
| 427 | + while ($row = $db->fetchObject($res)) { |
| 428 | + $result[] = SMWWikiPageValue::makePage($row->title, $row->namespace, $row->sortkey); |
| 429 | + } |
| 430 | + $db->freeResult($res); |
| 431 | + wfProfileOut("SMWSQLStore2::getPropertySubjects (SMW)"); |
| 432 | + return $result; |
| 433 | + } |
| 434 | + |
| 435 | + |
| 436 | + /** |
| 437 | + * Helper function to compute from and where strings for a DB query so that |
| 438 | + * only rows of the given value object match. The parameter $tableindex |
| 439 | + * counts that tables used in the query to avoid duplicate table names. The |
| 440 | + * parameter $proptable provides the SMWSQLStore2Table object that is |
| 441 | + * queried. |
| 442 | + * |
| 443 | + * @todo Maybe do something about redirects. The old code was |
| 444 | + // $oid = $this->getSMWPageID($value->getDBkey(),$value->getNamespace(),$value->getInterwiki(),false); |
| 445 | + ///NOTE: we do not use the canonical (redirect-aware) id here! |
| 446 | + */ |
| 447 | + protected function prepareValueQuery(&$from, &$where, $proptable, $value, $tableindex=1) { |
| 448 | + $db =& wfGetDB( DB_SLAVE ); |
| 449 | + if ($value instanceof SMWContainerValue) { // recursive handling of containers |
| 450 | + $joinfield = "t$tableindex." . reset(array_keys($proptable->objectfields)); // this must be a type 'p' object |
| 451 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 452 | + foreach ($value->getData()->getProperties() as $subproperty) { |
| 453 | + $tableid = SMWSQLStore2::findPropertyTableID($subproperty); |
| 454 | + if ( ($tableid == '') && ($value!==null) ) { // maybe a type-polymorphic property like _1; use value to find type |
| 455 | + $tableid = SMWSQLStore2::findTypeTableID(reset($value->getData()->getPropertyValues($subproperty))->getTypeID()); |
542 | 456 | } |
543 | | - } |
544 | | - break; |
545 | | - case SMW_SQL2_ATTS2: |
546 | | - $table = 'smw_atts2'; |
547 | | - if ($value !== null) { |
548 | | - $keys = $value->getDBkeys(); |
549 | | - $sql .= ' AND value_xsd=' . $db->addQuotes($keys[0]) . |
550 | | - ' AND value_unit=' . $db->addQuotes($value->getUnit()); |
551 | | - } |
552 | | - break; |
553 | | - case SMW_SQL2_SPEC2: |
554 | | - $table = 'smw_spec2'; |
555 | | - if ($value !== null) { |
556 | | - $keys = $value->getDBkeys(); |
557 | | - $sql .= ' AND value_string=' . $db->addQuotes($keys[0]); |
558 | | - } |
559 | | - break; |
560 | | - case SMW_SQL2_REDI2: |
561 | | - $oid = $this->getSMWPageID($value->getDBkey(),$value->getNamespace(),$value->getInterwiki(),false); |
562 | | - /// NOTE: we do not use the canonical (redirect-aware) id here! |
563 | | - /// NOTE: we ignore sortkeys here -- this should be ok |
564 | | - if ($oid != 0) { |
565 | | - $res = $db->select( array('smw_redi2'), 's_title,s_namespace', |
566 | | - 'o_id=' . $db->addQuotes($oid), |
567 | | - 'SMW::getSpecialSubjects', $this->getSQLOptions($requestoptions) ); |
568 | | - while($row = $db->fetchObject($res)) { |
569 | | - $dv = SMWWikiPageValue::makePage($row->s_title, $row->s_namespace); |
570 | | - $result[] = $dv; |
| 457 | + $subproptable = $proptables[$tableid]; |
| 458 | + foreach ($value->getData()->getPropertyValues($subproperty) as $subvalue) { |
| 459 | + $tableindex++; |
| 460 | + if ($subproptable->idsubject) { // simply add property table to check values |
| 461 | + $from .= " INNER JOIN " . $db->tableName($subproptable->name) . " AS t$tableindex ON t$tableindex.s_id=$joinfield"; |
| 462 | + } else { // exotic case with table that uses subject title+namespace in container object (should never happen in SMW core) |
| 463 | + $from .= " INNER JOIN " . $db->tableName('smw_ids') . " AS ids$tableindex ON ids$tableindex.smw_id=$joinfield" . |
| 464 | + " INNER JOIN " . $db->tableName($subproptable->name) . " AS t$tableindex ON " . |
| 465 | + "t$tableindex.s_title=ids$tableindex.smw_title AND t$tableindex.s_namespace=ids$tableindex.smw_namespace"; |
| 466 | + } |
| 467 | + if ($subproptable->fixedproperty == false) { // the ID we get should be !=0, so no point in filtering the converse |
| 468 | + $where .= ($where?' AND ':'') . "t$tableindex.p_id=" . $db->addQuotes($this->getSMWPropertyID($subproperty)); |
| 469 | + } |
| 470 | + $this->prepareValueQuery($from,$where,$subproptable,$subvalue,$tableindex); |
571 | 471 | } |
572 | | - $db->freeResult($res); |
573 | 472 | } |
574 | | - break; |
575 | | - case SMW_SQL2_NARY2: |
576 | | - if ($value === null) { // no value -- handled just like for wikipage |
577 | | - $table = 'smw_rels2'; |
578 | | - break; |
579 | | - } |
580 | | - $values = $value->getDVs(); |
581 | | - $smw_rels2 = $db->tableName('smw_rels2'); |
582 | | - $smw_ids = $db->tableName('smw_ids'); |
583 | | - // build a single SQL query for that |
584 | | - $where = "t.p_id=" . $db->addQuotes($pid); |
585 | | - $from = "$smw_rels2 AS t INNER JOIN $smw_ids AS i ON t.s_id=i.smw_id"; |
586 | | - $count = 0; |
587 | | - foreach ($values as $dv) { |
588 | | - if ( ($dv === null) || (!$dv->isValid()) ) { |
589 | | - $count++; |
590 | | - continue; |
| 473 | + } elseif ($value !== null) { // add conditions for given value |
| 474 | + $dbkeys = $value->getDBkeys(); |
| 475 | + $i = 0; |
| 476 | + foreach ($proptable->objectfields as $fieldname => $typeid) { |
| 477 | + if ($i>=count($dbkeys)) break; |
| 478 | + if ($typeid == 'p') { // Special case: page id, resolve this in advance |
| 479 | + $oid = $this->getSMWPageID($dbkeys[$i],$dbkeys[$i+1],$dbkeys[$i+2]); |
| 480 | + $i += 3; // skip these additional values (sortkey not needed here) |
| 481 | + $where .= ($where?' AND ':'') . "t$tableindex.$fieldname=" . $db->addQuotes($oid); |
| 482 | + } elseif ($typeif != 'l') { // plain value, but not a text blob |
| 483 | + $where .= ($where?' AND ':'') . "t$tableindex.$fieldname=" . $db->addQuotes($dbkeys[$i]); |
591 | 484 | } |
592 | | - $npid = $this->makeSMWPageID(strval($count),SMW_NS_PROPERTY,SMW_SQL2_SMWIW); // might be cached; FIXME: make some of those predefined + important! |
593 | | - switch (SMWSQLStore2::getStorageMode($dv->getTypeID())) { |
594 | | - case SMW_SQL2_RELS2: |
595 | | - $from .= " INNER JOIN $smw_rels2 AS t$count ON t.o_id=t$count.s_id INNER JOIN $smw_ids AS i$count ON t$count.o_id=i$count.smw_id"; |
596 | | - $where .= " AND t$count.p_id=" . $db->addQuotes($npid) . |
597 | | - " AND i$count.smw_title=" . $db->addQuotes($dv->getDBkey()) . |
598 | | - " AND i$count.smw_namespace=" . $db->addQuotes($dv->getNamespace()) . |
599 | | - " AND i$count.smw_iw=" . $db->addQuotes(''); |
600 | | - break; |
601 | | - case SMW_SQL2_ATTS2: |
602 | | - $keys = $dv->getDBkeys(); |
603 | | - $from .= ' INNER JOIN ' . $db->tableName('smw_atts2') . " AS t$count ON t.o_id=t$count.s_id"; |
604 | | - $where .= " AND t$count.p_id=" . $db->addQuotes($npid) . |
605 | | - " AND t$count.value_xsd=" . $db->addQuotes($keys[0]) . |
606 | | - " AND t$count.value_unit=" . $db->addQuotes($dv->getUnit()); |
607 | | - } |
608 | | - $count++; |
609 | | - // default: not supported (including text and code) |
| 485 | + $i++; |
610 | 486 | } |
611 | | - $res = $db->query("SELECT DISTINCT i.smw_title AS title,i.smw_namespace AS namespace,i.smw_sortkey AS sortkey FROM $from WHERE $where", 'SMW::getPropertySubjects', $this->getSQLOptions($requestoptions,'smw_sortkey')); |
612 | | - while($row = $db->fetchObject($res)) { |
613 | | - $result[] = SMWWikiPageValue::makePage($row->title, $row->namespace, $row->sortkey); |
614 | | - } |
615 | | - $db->freeResult($res); |
616 | | - break; |
617 | 487 | } |
618 | | - |
619 | | - if ($table != '') { |
620 | | - $res = $db->select( array($table,'smw_ids'), |
621 | | - 'DISTINCT smw_title,smw_namespace,smw_sortkey', |
622 | | - 's_id=smw_id' . ($sql?' AND ':'') . $sql . $this->getSQLConditions($requestoptions,'smw_sortkey','smw_sortkey'), |
623 | | - 'SMW::getPropertySubjects', |
624 | | - $this->getSQLOptions($requestoptions,'smw_sortkey') ); |
625 | | - while ($row = $db->fetchObject($res)) { |
626 | | - $dv = SMWWikiPageValue::makePage($row->smw_title, $row->smw_namespace, $row->smw_sortkey); |
627 | | - $result[] = $dv; |
628 | | - } |
629 | | - $db->freeResult($res); |
630 | | - } |
631 | | - wfProfileOut("SMWSQLStore2::getPropertySubjects (SMW)"); |
632 | | - return $result; |
633 | 488 | } |
634 | 489 | |
635 | | - function getAllPropertySubjects(SMWPropertyValue $property, $requestoptions = null) { |
| 490 | + public function getAllPropertySubjects(SMWPropertyValue $property, $requestoptions = null) { |
636 | 491 | wfProfileIn("SMWSQLStore2::getAllPropertySubjects (SMW)"); |
637 | 492 | $result = $this->getPropertySubjects($property, null, $requestoptions); |
638 | 493 | wfProfileOut("SMWSQLStore2::getAllPropertySubjects (SMW)"); |
639 | 494 | return $result; |
640 | 495 | } |
641 | 496 | |
642 | | - function getProperties($subject, $requestoptions = null) { |
| 497 | + /** |
| 498 | + * @todo Restrict this function to SMWWikiPageValue subjects. |
| 499 | + */ |
| 500 | + public function getProperties($subject, $requestoptions = null) { |
643 | 501 | wfProfileIn("SMWSQLStore2::getProperties (SMW)"); |
644 | 502 | $sid = $this->getSMWPageID($subject->getDBkey(), $subject->getNamespace(),$subject->getInterwiki()); |
645 | | - if ($sid == 0) { |
| 503 | + if ($sid == 0) { // no id, no page, no properties |
646 | 504 | wfProfileOut("SMWSQLStore2::getProperties (SMW)"); |
647 | 505 | return array(); |
648 | 506 | } |
649 | 507 | |
650 | 508 | $db =& wfGetDB( DB_SLAVE ); |
651 | | - $sql = 's_id=' . $db->addQuotes($sid) . ' AND p_id=smw_id' . $this->getSQLConditions($requestoptions,'smw_sortkey','smw_sortkey'); |
652 | | - |
653 | 509 | $result = array(); |
654 | | - /// NOTE: the following also includes naries, which are now kept in smw_rels2 |
655 | | - foreach (array('smw_atts2','smw_text2','smw_rels2','smw_spec2') as $table) { |
656 | | - $res = $db->select( array($table,'smw_ids'), 'DISTINCT smw_title', |
657 | | - $sql, 'SMW::getProperties', $this->getSQLOptions($requestoptions,'smw_sortkey') ); |
658 | | - while($row = $db->fetchObject($res)) { |
659 | | - $result[] = SMWPropertyValue::makeProperty($row->smw_title); |
| 510 | + if ($requestoptions !== null) { // potentially need to get more results, since options apply to union |
| 511 | + $suboptions = clone $requestoptions; |
| 512 | + $suboptions->limit = $requestoptions->limit + $requestoptions->offset; |
| 513 | + $suboptions->offset = 0; |
| 514 | + } else { |
| 515 | + $suboptions = null; |
| 516 | + } |
| 517 | + foreach (SMWSQLStore2::getPropertyTables() as $tid => $proptable) { |
| 518 | + $from = $db->tableName($proptable->name); |
| 519 | + if ($proptable->idsubject) { |
| 520 | + $where = 's_id=' . $db->addQuotes($sid); |
| 521 | + } elseif ($subject->getInterwiki() == '') { |
| 522 | + $where = 's_title=' . $db->addQuotes($subject->getDBkey()) . ' AND s_namespace=' . $db->addQuotes($subject->getNamespace()); |
| 523 | + } else { // subjects with non-emtpy interwiki cannot have properties |
| 524 | + continue; |
660 | 525 | } |
| 526 | + if ($proptable->fixedproperty == false) { // select all properties |
| 527 | + $from .= " INNER JOIN " . $db->tableName('smw_ids') . " ON smw_id=p_id"; |
| 528 | + $res = $db->select( $from, 'DISTINCT smw_title,smw_sortkey', // select sortkey since it might be used in ordering (needed by Postgres) |
| 529 | + $where . $this->getSQLConditions($suboptions,'smw_sortkey','smw_sortkey'), |
| 530 | + 'SMW::getProperties', $this->getSQLOptions($suboptions,'smw_sortkey') ); |
| 531 | + while($row = $db->fetchObject($res)) { |
| 532 | + $result[] = SMWPropertyValue::makeProperty($row->smw_title); |
| 533 | + } |
| 534 | + } else { // just check if subject occurs in table |
| 535 | + $res = $db->select( $from, '*', $where, 'SMW::getProperties', array('LIMIT' => 1) ); |
| 536 | + if ($db->numRows($res) > 0) { |
| 537 | + $result[] = SMWPropertyValue::makeProperty($proptable->fixedproperty); |
| 538 | + } |
| 539 | + } |
661 | 540 | $db->freeResult($res); |
662 | 541 | } |
| 542 | + $result = $this->applyRequestOptions($result,$requestoptions); // apply options to overall result |
663 | 543 | wfProfileOut("SMWSQLStore2::getProperties (SMW)"); |
664 | 544 | return $result; |
665 | 545 | } |
666 | 546 | |
667 | | - /** |
668 | | - * @todo This function is currently implemented only for values of type Page ('_wpg'). |
669 | | - */ |
670 | | - function getInProperties(SMWDataValue $value, $requestoptions = null) { |
| 547 | + public function getInProperties(SMWDataValue $value, $requestoptions = null) { |
671 | 548 | wfProfileIn("SMWSQLStore2::getInProperties (SMW)"); |
672 | 549 | $db =& wfGetDB( DB_SLAVE ); |
673 | 550 | $result = array(); |
674 | | - if (SMWSQLStore2::getStorageMode($value->getTypeID()) == SMW_SQL2_RELS2) { |
675 | | - $oid = $this->getSMWPageID($value->getDBkey(),$value->getNamespace(),$value->getInterwiki()); |
676 | | - $sql = 'p_id=smw_id AND o_id=' . $db->addQuotes($oid) . |
677 | | - ' AND smw_iw=' . $db->addQuotes('') . // only local, non-internal properties |
678 | | - $this->getSQLConditions($requestoptions,'smw_sortkey','smw_sortkey'); |
679 | | - $res = $db->select( array('smw_rels2','smw_ids'), 'DISTINCT smw_title, smw_sortkey', |
680 | | - $sql, 'SMW::getInProperties', $this->getSQLOptions($requestoptions,'smw_sortkey') ); |
681 | | - while($row = $db->fetchObject($res)) { |
682 | | - $result[] = SMWPropertyValue::makeProperty($row->smw_title); |
| 551 | + |
| 552 | + if ($requestoptions !== null) { // potentially need to get more results, since options apply to union |
| 553 | + $suboptions = clone $requestoptions; |
| 554 | + $suboptions->limit = $requestoptions->limit + $requestoptions->offset; |
| 555 | + $suboptions->offset = 0; |
| 556 | + } else { |
| 557 | + $suboptions = null; |
| 558 | + } |
| 559 | + foreach (SMWSQLStore2::getPropertyTables() as $tid => $proptable) { |
| 560 | + $select = $where = $from = ''; |
| 561 | + if ($proptable->fixedproperty == false) { // join smw_ids to get property titles |
| 562 | + $from = $db->tableName('smw_ids') . " INNER JOIN " . $db->tableName($proptable->name) . " AS t1 ON t1.p_id=smw_id"; |
| 563 | + $this->prepareValueQuery($from,$where,$proptable,$value,1); |
| 564 | + $res = $db->select( $from, 'DISTINCT smw_title,smw_sortkey', // select sortkey since it might be used in ordering (needed by Postgres) |
| 565 | + $where . $this->getSQLConditions($suboptions,'smw_sortkey','smw_sortkey',$where!=''), |
| 566 | + 'SMW::getInProperties', $this->getSQLOptions($suboptions,'smw_sortkey') ); |
| 567 | + while ($row = $db->fetchObject($res)) { |
| 568 | + $result[] = SMWPropertyValue::makeProperty($row->smw_title); |
| 569 | + } |
| 570 | + } else { |
| 571 | + $from = $db->tableName($proptable->name) . " AS t1"; |
| 572 | + $this->prepareValueQuery($from,$where,$proptable,$value,1); |
| 573 | + $res = $db->select( $from, '*', $where, 'SMW::getProperties', array('LIMIT' => 1) ); |
| 574 | + if ($db->numRows($res) > 0) { |
| 575 | + $result[] = SMWPropertyValue::makeProperty($proptable->fixedproperty); |
| 576 | + } |
683 | 577 | } |
684 | 578 | $db->freeResult($res); |
685 | 579 | } |
| 580 | + $result = $this->applyRequestOptions($result,$requestoptions); // apply options to overall result |
686 | 581 | wfProfileOut("SMWSQLStore2::getInProperties (SMW)"); |
687 | 582 | return $result; |
688 | 583 | } |
689 | 584 | |
690 | 585 | ///// Writing methods ///// |
691 | 586 | |
692 | | - function deleteSubject(Title $subject) { |
| 587 | + public function deleteSubject(Title $subject) { |
693 | 588 | wfProfileIn('SMWSQLStore2::deleteSubject (SMW)'); |
694 | 589 | $this->deleteSemanticData(SMWWikiPageValue::makePageFromTitle($subject)); |
695 | 590 | $this->updateRedirects($subject->getDBkey(), $subject->getNamespace()); // also delete redirects, may trigger update jobs! |
— | — | @@ -705,7 +600,7 @@ |
706 | 601 | wfProfileOut('SMWSQLStore2::deleteSubject (SMW)'); |
707 | 602 | } |
708 | 603 | |
709 | | - function updateData(SMWSemanticData $data) { |
| 604 | + public function updateData(SMWSemanticData $data) { |
710 | 605 | wfProfileIn("SMWSQLStore2::updateData (SMW)"); |
711 | 606 | $subject = $data->getSubject(); |
712 | 607 | $this->deleteSemanticData($subject); |
— | — | @@ -720,137 +615,19 @@ |
721 | 616 | } |
722 | 617 | // always make an ID (pages without ID cannot be in query results, not even in fixed value queries!): |
723 | 618 | $sid = $this->makeSMWPageID($subject->getDBkey(),$subject->getNamespace(),'',true,$subject->getSortkey()); |
724 | | - $db =& wfGetDB( DB_MASTER ); |
| 619 | + $updates = array(); // collect data for bulk updates; format: tableid => updatearray |
| 620 | + $this->prepareDBUpdates($updates,$data,$sid); |
725 | 621 | |
726 | | - // do bulk updates: |
727 | | - $up_rels2 = array(); $up_atts2 = array(); |
728 | | - $up_text2 = array(); $up_spec2 = array(); |
729 | | - $up_subs2 = array(); $up_inst2 = array(); |
730 | | - $up_subp2 = array(); |
731 | | - $concept_desc = null; // this gets a special treatment |
732 | | - |
733 | | - foreach($data->getProperties() as $property) { |
734 | | - $propertyValueArray = $data->getPropertyValues($property); |
735 | | - $mode = SMWSQLStore2::getStorageMode($property->getPropertyTypeID()); |
736 | | - foreach($propertyValueArray as $value) { |
737 | | - if (!$value->isValid()) continue; // errors are already recorded in valid values, no need to store them here |
738 | | - switch ($mode) { |
739 | | - case SMW_SQL2_REDI2: break; // handled above |
740 | | - case SMW_SQL2_INST2: |
741 | | - $up_inst2[] = array( |
742 | | - 's_id' => $sid, |
743 | | - 'o_id' => $this->makeSMWPageID($value->getDBkey(),$value->getNamespace(),'')); |
744 | | - break; |
745 | | - case SMW_SQL2_SUBS2: |
746 | | - $up_subs2[] = array( |
747 | | - 's_id' => $sid, |
748 | | - 'o_id' => $this->makeSMWPageID($value->getDBkey(),$value->getNamespace(),'')); |
749 | | - break; |
750 | | - case SMW_SQL2_SUBP2: |
751 | | - $up_subp2[] = array( |
752 | | - 's_id' => $sid, |
753 | | - 'o_id' => $this->makeSMWPageID($value->getDBkey(),$value->getNamespace(),'')); |
754 | | - break; |
755 | | - case SMW_SQL2_CONC2: |
756 | | - $concept_desc = end($propertyValueArray); // only one value per page! |
757 | | - break; |
758 | | - case SMW_SQL2_SPEC2: |
759 | | - $keys = $value->getDBkeys(); |
760 | | - $up_spec2[] = array( |
761 | | - 's_id' => $sid, |
762 | | - 'p_id' => $this->makeSMWPropertyID($property), |
763 | | - 'value_string' => $keys[0]); |
764 | | - break; |
765 | | - case SMW_SQL2_TEXT2: |
766 | | - $keys = $value->getDBkeys(); |
767 | | - $up_text2[] = array( |
768 | | - 's_id' => $sid, |
769 | | - 'p_id' => $this->makeSMWPropertyID($property), |
770 | | - 'value_blob' => $keys[0] ); |
771 | | - break; |
772 | | - case SMW_SQL2_RELS2: |
773 | | - $up_rels2[] = array( |
774 | | - 's_id' => $sid, |
775 | | - 'p_id' => $this->makeSMWPropertyID($property), |
776 | | - 'o_id' => $this->makeSMWPageID($value->getDBkey(),$value->getNamespace(),$value->getInterwiki()) ); |
777 | | - break; |
778 | | - case SMW_SQL2_ATTS2: |
779 | | - $keys = $value->getDBkeys(); |
780 | | - $up_atts2[] = array( |
781 | | - 's_id' => $sid, |
782 | | - 'p_id' => $this->makeSMWPropertyID($property), |
783 | | - 'value_unit' => $value->getUnit(), |
784 | | - 'value_xsd' => $keys[0], |
785 | | - 'value_num' => $value->getNumericValue() ); |
786 | | - break; |
787 | | - case SMW_SQL2_NARY2: |
788 | | - $bnode = $this->makeSMWBnodeID($sid); |
789 | | - $up_rels2[] = array( |
790 | | - 's_id' => $sid, |
791 | | - 'p_id' => $this->makeSMWPropertyID($property), |
792 | | - 'o_id' => $bnode ); |
793 | | - $npos = 0; |
794 | | - foreach ($value->getDVs() as $dv) { |
795 | | - if ( ($dv !== null) && ($dv->isValid()) ) { |
796 | | - $pid = $this->makeSMWPageID(strval($npos),SMW_NS_PROPERTY,SMW_SQL2_SMWIW); // TODO: predefine some of those ( |
797 | | - switch (SMWSQLStore2::getStorageMode($dv->getTypeID())) { |
798 | | - case SMW_SQL2_RELS2: |
799 | | - $up_rels2[] = array( |
800 | | - 's_id' => $bnode, |
801 | | - 'p_id' => $pid, |
802 | | - 'o_id' => $this->makeSMWPageID($dv->getDBkey(),$dv->getNamespace(),$dv->getInterwiki()) ); |
803 | | - break; |
804 | | - case SMW_SQL2_TEXT2: |
805 | | - $keys = $dv->getDBkeys(); |
806 | | - $up_text2[] = array( |
807 | | - 's_id' => $bnode, |
808 | | - 'p_id' => $pid, |
809 | | - 'value_blob' => $keys[0] ); |
810 | | - break; |
811 | | - case SMW_SQL2_ATTS2: |
812 | | - $keys = $dv->getDBkeys(); |
813 | | - $up_atts2[] = array( |
814 | | - 's_id' => $bnode, |
815 | | - 'p_id' => $pid, |
816 | | - 'value_unit' => $dv->getUnit(), |
817 | | - 'value_xsd' => $keys[0], |
818 | | - 'value_num' => $dv->getNumericValue() ); |
819 | | - break; |
820 | | - } |
821 | | - //default: no other cases expected/supported |
822 | | - } |
823 | | - $npos++; |
824 | | - } |
825 | | - break; |
826 | | - } |
827 | | - } |
| 622 | + $db =& wfGetDB( DB_MASTER ); |
| 623 | + foreach ($updates as $tablename => $uvals) { |
| 624 | + $db->insert( $tablename, $uvals, "SMW::updateData$tablename"); |
828 | 625 | } |
829 | 626 | |
830 | | - // write to DB: |
831 | | - if (count($up_rels2) > 0) { |
832 | | - $db->insert( 'smw_rels2', $up_rels2, 'SMW::updateRel2Data'); |
833 | | - } |
834 | | - if (count($up_atts2) > 0) { |
835 | | - $db->insert( 'smw_atts2', $up_atts2, 'SMW::updateAtt2Data'); |
836 | | - } |
837 | | - if (count($up_text2) > 0) { |
838 | | - $db->insert( 'smw_text2', $up_text2, 'SMW::updateText2Data'); |
839 | | - } |
840 | | - if (count($up_spec2) > 0) { |
841 | | - $db->insert( 'smw_spec2', $up_spec2, 'SMW::updateSpec2Data'); |
842 | | - } |
843 | | - if (count($up_subs2) > 0) { |
844 | | - $db->insert( 'smw_subs2', $up_subs2, 'SMW::updateSubs2Data'); |
845 | | - } |
846 | | - if (count($up_subp2) > 0) { |
847 | | - $db->insert( 'smw_subp2', $up_subp2, 'SMW::updateSubp2Data'); |
848 | | - } |
849 | | - if (count($up_inst2) > 0) { |
850 | | - $db->insert( 'smw_inst2', $up_inst2, 'SMW::updateInst2Data'); |
851 | | - } |
852 | 627 | // Concepts are not just written but carefully updated, |
853 | 628 | // preserving existing metadata (cache ...) for a concept: |
854 | 629 | if ( $subject->getNamespace() == SMW_NS_CONCEPT ) { |
| 630 | + $property = SMWPropertyValue::makeProperty('_CONC'); |
| 631 | + $concept_desc = end($data->getPropertyValues($property)); |
855 | 632 | if ( ($concept_desc !== null) && ($concept_desc->isValid()) ) { |
856 | 633 | $up_conc2 = array( |
857 | 634 | 'concept_txt' => $concept_desc->getConceptText(), |
— | — | @@ -882,79 +659,141 @@ |
883 | 660 | } |
884 | 661 | } |
885 | 662 | |
886 | | - $this->m_semdata[$sid] = clone $data; // update cache, important if jobs are directly following this call |
887 | | - $this->m_sdstate[$sid] = 0xFFFFFFFF; // everything that one can know |
| 663 | + // Finally update caches (may be important if jobs are directly following this call) |
| 664 | + $this->m_semdata[$sid] = clone $data; |
| 665 | + $this->m_sdstate[$sid] = array_keys(SMWSQLStore2::getPropertyTables()); // everything that one can know |
888 | 666 | wfProfileOut("SMWSQLStore2::updateData (SMW)"); |
889 | 667 | } |
890 | 668 | |
891 | | - function changeTitle(Title $oldtitle, Title $newtitle, $pageid, $redirid=0) { |
| 669 | + /** |
| 670 | + * Extend the given update array to account for the data in the SMWSemanticData object. |
| 671 | + * The subject page of the data container is ignored, and the given $pageid is used directly. |
| 672 | + * However, if the subject is empty, then a blank node (internal id) is generated instead |
| 673 | + * of using the given $pageid directly (note that internal objects always belong to one |
| 674 | + * proper object which in this case is the given $pageid). |
| 675 | + * |
| 676 | + * The function returns the id that was used for writing. Especially, any newly created |
| 677 | + * internal id is returned. |
| 678 | + */ |
| 679 | + protected function prepareDBUpdates(&$updates, SMWSemanticData $data, $pageid) { |
| 680 | + $subject = $data->getSubject(); |
| 681 | + $sid = ( $subject !== null)?$pageid:$this->makeSMWBnodeID($pageid); |
| 682 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 683 | + foreach($data->getProperties() as $property) { |
| 684 | + $tableid = SMWSQLStore2::findPropertyTableID($property); |
| 685 | + if (!$tableid) { // happens when table is not determined by property; use values to find type |
| 686 | + $dv = reset($data->getPropertyValues($property)); |
| 687 | + $tableid = SMWSQLStore2::findTypeTableID($dv->getTypeID()); |
| 688 | + } |
| 689 | + if (!$tableid) { // can't store this data, sorry |
| 690 | + return $sid; |
| 691 | + } |
| 692 | + $proptable = $proptables[$tableid]; |
| 693 | + foreach($data->getPropertyValues($property) as $dv) { |
| 694 | + if (!$dv->isValid() || ($tableid == 'smw_redi2')) continue; |
| 695 | + // errors are already recorded separately, no need to store them here; |
| 696 | + // redirects were treated above |
| 697 | + ///TODO check needed if subject is null (would happen if a user defined proptable with !idsubject was used on an internal object -- currently this is not possible |
| 698 | + $uvals = ($proptable->idsubject)?array('s_id' => $sid): |
| 699 | + array('s_title' => $subject->getDBkey(), 's_namespace' => $subject->getNamespace()); |
| 700 | + if ($proptable->fixedproperty == false) { |
| 701 | + $uvals['p_id'] = $this->makeSMWPropertyID($property); |
| 702 | + } |
| 703 | + if ($dv instanceof SMWContainerValue) { // process subobjects recursively |
| 704 | + $bnode = $this->prepareDBUpdates($updates, $dv->getData(), $pageid); |
| 705 | + //Note: tables for container objects MUST have objectfields == array(<somename> => 'p') |
| 706 | + reset($proptable->objectfields); |
| 707 | + $uvals[key($proptable->objectfields)] = $bnode; |
| 708 | + } else { |
| 709 | + $dbkeys = $dv->getDBkeys(); |
| 710 | + reset($dbkeys); |
| 711 | + foreach ($proptable->objectfields as $fieldname => $typeid) { |
| 712 | + if ($typeid != 'p') { |
| 713 | + $uvals[$fieldname] = current($dbkeys); |
| 714 | + } else { |
| 715 | + $title = current($dbkeys); |
| 716 | + $namespace = next($dbkeys); |
| 717 | + $iw = next($dbkeys); |
| 718 | + $sortkey = next($dbkeys); // not used; sortkeys are not set on writing objects |
| 719 | + $uvals[$fieldname] = $this->makeSMWPageID($title,$namespace,$iw); |
| 720 | + } |
| 721 | + next($dbkeys); |
| 722 | + } |
| 723 | + } |
| 724 | + if (!array_key_exists($proptable->name,$updates)) { |
| 725 | + $updates[$proptable->name] = array(); |
| 726 | + } |
| 727 | + $updates[$proptable->name][] = $uvals; |
| 728 | + } |
| 729 | + } |
| 730 | + return $sid; |
| 731 | + } |
| 732 | + |
| 733 | + /** |
| 734 | + * Implementation of SMWStore::changeTitle(). In contrast to |
| 735 | + * updateRedirects(), this function does not simply write a redirect |
| 736 | + * from the old page to the new one, but also deletes all data that may |
| 737 | + * already be stored for the new title (normally the new title should |
| 738 | + * belong to an empty page that has no data but at least it could have a |
| 739 | + * redirect to the old page), and moves all data that exists for the old |
| 740 | + * title to the new location. Thus, the function executes three steps: |
| 741 | + * delete data at newtitle, move data from oldtitle to newtitle, and set |
| 742 | + * redirect from oldtitle to newtitle. In some cases, the goal can be |
| 743 | + * achieved more efficiently, e.g. if the new title does not occur in SMW |
| 744 | + * yet: then we can just change the ID records for the titles instead of |
| 745 | + * changing all data tables |
| 746 | + * |
| 747 | + * Note that the implementation ignores the MediaWiki IDs since this |
| 748 | + * store has its own ID management. Also, the function requires that both |
| 749 | + * titles are local, i.e. have empty interwiki prefix. |
| 750 | + * |
| 751 | + * @todo Currently the sortkey is not moved with the remaining data. It is |
| 752 | + * not possible to move it reliably in all cases: we cannot distinguish an |
| 753 | + * unset sortkey from one that was set to the name of oldtitle. Maybe use |
| 754 | + * update jobs right away? |
| 755 | + */ |
| 756 | + public function changeTitle(Title $oldtitle, Title $newtitle, $pageid, $redirid=0) { |
| 757 | + global $smwgQEqualitySupport; |
892 | 758 | wfProfileIn("SMWSQLStore2::changeTitle (SMW)"); |
893 | | - ///NOTE: this function ignores the given MediaWiki IDs (this store has its own IDs) |
894 | | - ///NOTE: this function assumes input titles to be local (no interwiki). Anything else would be too gross. |
895 | | - $sid_c = $this->getSMWPageID($oldtitle->getDBkey(),$oldtitle->getNamespace(),''); |
| 759 | + // get IDs but do not resolve redirects: |
896 | 760 | $sid = $this->getSMWPageID($oldtitle->getDBkey(),$oldtitle->getNamespace(),'',false); |
897 | | - $tid_c = $this->getSMWPageID($newtitle->getDBkey(),$newtitle->getNamespace(),''); |
898 | 761 | $tid = $this->getSMWPageID($newtitle->getDBkey(),$newtitle->getNamespace(),'',false); |
899 | | - |
900 | 762 | $db =& wfGetDB( DB_MASTER ); |
901 | 763 | |
902 | | - if ($tid_c == 0) { // target not used anywhere yet, just hijack its title for our current id |
903 | | - /// NOTE: given our lazy id management, this condition may not hold, even if $newtitle is an unused new page |
904 | | - if ($sid != 0) { // move only if id exists at all |
905 | | - $cond_array = array( 'smw_id' => $sid ); |
906 | | - $val_array = array( 'smw_title' => $newtitle->getDBkey(), |
907 | | - 'smw_namespace' => $newtitle->getNamespace()); |
908 | | - $db->update('smw_ids', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
| 764 | + if ( ($tid == 0) && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { // target not used anywhere yet, just hijack its title for our current id |
| 765 | + // This condition may not hold even if $newtitle is currently unused/non-existing since we keep old IDs. |
| 766 | + // If equality support is off, then this simple move does too much; fall back to general case below. |
| 767 | + if ($sid != 0) { // change id entry to refer to the new title |
| 768 | + $db->update('smw_ids', array( 'smw_title' => $newtitle->getDBkey(), 'smw_namespace' => $newtitle->getNamespace(), 'smw_iw' => '' ), |
| 769 | + array( 'smw_id' => $sid ), 'SMWSQLStore2::changeTitle'); |
909 | 770 | } else { // make new (target) id for use in redirect table |
910 | | - $sid = $this->makeSMWPageID($newtitle->getDBkey(),$newtitle->getNamespace(),''); // make target id |
| 771 | + $sid = $this->makeSMWPageID($newtitle->getDBkey(),$newtitle->getNamespace(),''); |
911 | 772 | } // at this point, $sid is the id of the target page (according to smw_ids) |
912 | 773 | $this->makeSMWPageID($oldtitle->getDBkey(),$oldtitle->getNamespace(),SMW_SQL2_SMWREDIIW); // make redirect id for oldtitle |
913 | | - // update redirects |
| 774 | + $db->insert( 'smw_redi2', array( 's_title' => $oldtitle->getDBkey(), 's_namespace' => $oldtitle->getNamespace(), 'o_id'=>$sid ), |
| 775 | + 'SMWSQLStore2::changeTitle'); |
| 776 | + $this->m_ids[" " . $oldtitle->getNamespace() . " " . $oldtitle->getDBkey() . " C"] = $sid; |
| 777 | + //$this->m_ids[" " . $oldtitle->getNamespace() . " " . $oldtitle->getDBkey() . " -"] = Already OK after makeSMWPageID above |
| 778 | + $this->m_ids[" " . $newtitle->getNamespace() . " " . $newtitle->getDBkey() . " C"] = $sid; |
| 779 | + $this->m_ids[" " . $newtitle->getNamespace() . " " . $newtitle->getDBkey() . " -"] = $sid; |
914 | 780 | /// NOTE: there is the (bad) case that the moved page is a redirect. As chains of |
915 | | - /// redirects are not supported by MW or SMW, the below is maximally correct there too. |
916 | | - $db->insert( |
917 | | - 'smw_redi2', |
918 | | - array( 's_title' => $oldtitle->getDBkey(), 's_namespace' => $oldtitle->getNamespace(), 'o_id'=>$sid ), |
919 | | - 'SMWSQLStore2::changeTitle' |
920 | | - ); |
| 781 | + /// redirects are not supported by MW or SMW, the above is maximally correct in this case too. |
921 | 782 | /// NOTE: this temporarily leaves existing redirects to oldtitle point to newtitle as well, which |
922 | 783 | /// will be lost after the next update. Since double redirects are an error anyway, this is not |
923 | | - /// a bad behaviour: everything will continue to work until the old redirect is updated, which |
924 | | - /// will hopefully be to fix the double redirect. |
925 | | - } else { |
926 | | - $this->deleteSemanticData(SMWWikiPageValue::makePageFromTitle($newtitle)); // should not have much effect, but let's be sure |
927 | | - $this->updateRedirects($newtitle->getDBkey(), $newtitle->getNamespace()); // delete these redirects, may trigger update jobs! |
928 | | - $this->updateRedirects($oldtitle->getDBkey(), $oldtitle->getNamespace(), $newtitle->getDBkey(), $newtitle->getNamespace()); |
929 | | - // also move subject data along (updateRedirects only cares about changes in objects/properties) |
| 784 | + /// a bad behaviour: everything will continue to work until the existing redirects are updated, |
| 785 | + /// which will hopefully be done to fix the double redirect. |
| 786 | + } else { // general move method that should be correct in all cases (equality support respected when updating redirects) |
| 787 | + // delete any existing data from new title: |
| 788 | + $this->deleteSemanticData(SMWWikiPageValue::makePageFromTitle($newtitle)); // $newtitle should not have data, but let's be sure |
| 789 | + $this->updateRedirects($newtitle->getDBkey(), $newtitle->getNamespace()); // may trigger update jobs! |
| 790 | + // move all data of old title to new position: |
930 | 791 | if ($sid != 0) { |
931 | | - $cond_array = array( 's_id' => $sid ); |
932 | | - $val_array = array( 's_id' => $tid ); |
933 | | - $db->update('smw_rels2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
934 | | - $db->update('smw_atts2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
935 | | - $db->update('smw_text2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
936 | | - $db->update('smw_inst2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
937 | | - if ( ( $oldtitle->getNamespace() == SMW_NS_PROPERTY ) && |
938 | | - ( $newtitle->getNamespace() == SMW_NS_PROPERTY ) ) { |
939 | | - $db->update('smw_subp2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
940 | | - } elseif ($oldtitle->getNamespace() == SMW_NS_PROPERTY) { |
941 | | - $db->delete('smw_subp2', $cond_array, 'SMWSQLStore2::changeTitle'); |
942 | | - } elseif ( ( $oldtitle->getNamespace() == NS_CATEGORY ) && |
943 | | - ( $newtitle->getNamespace() == NS_CATEGORY ) ) { |
944 | | - $db->update('smw_subs2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
945 | | - } elseif ($oldtitle->getNamespace() == NS_CATEGORY) { |
946 | | - $db->delete('smw_subs2', $cond_array, 'SMWSQLStore2::changeTitle'); |
947 | | - } elseif ( ( $oldtitle->getNamespace() == SMW_NS_CONCEPT ) && |
948 | | - ( $newtitle->getNamespace() == SMW_NS_CONCEPT ) ) { |
949 | | - $db->update('smw_conc2', $val_array, $cond_array, 'SMWSQLStore2::changeTitle'); |
950 | | - $db->update('smw_conccache', array('o_id' => $tid), array('o_id' => $sid), 'SMWSQLStore2::changeTitle'); |
951 | | - } elseif ($oldtitle->getNamespace() == SMW_NS_CONCEPT) { |
952 | | - $db->delete('smw_conc2', $cond_array, 'SMWSQLStore2::changeTitle'); |
953 | | - $db->delete('smw_conccache', array('o_id' => $sid), 'SMWSQLStore2::changeTitle'); |
954 | | - } |
| 792 | + $this->changeSMWPageID($sid,$tid,$oldtitle->getNamespace(),$newtitle->getNamespace(), true, false); |
955 | 793 | } |
| 794 | + // now write a redirect from old title to new one; this also updates references in other tables as needed |
| 795 | + $this->updateRedirects($oldtitle->getDBkey(), $oldtitle->getNamespace(), $newtitle->getDBkey(), $newtitle->getNamespace()); |
956 | 796 | /// TODO: may not be optimal for the standard case that newtitle existed and redirected to oldtitle (PERFORMANCE) |
957 | 797 | } |
958 | | - |
959 | 798 | wfProfileOut("SMWSQLStore2::changeTitle (SMW)"); |
960 | 799 | } |
961 | 800 | |
— | — | @@ -972,30 +811,27 @@ |
973 | 812 | |
974 | 813 | ///// Special page functions ///// |
975 | 814 | |
976 | | - function getPropertiesSpecial($requestoptions = null) { |
| 815 | + /** |
| 816 | + * @todo Properties that are stored in dedicated tables |
| 817 | + * (SMWSQLStore2Table::fixedproperty) are currently ignored. |
| 818 | + */ |
| 819 | + public function getPropertiesSpecial($requestoptions = null) { |
977 | 820 | wfProfileIn("SMWSQLStore2::getPropertiesSpecial (SMW)"); |
978 | 821 | $db =& wfGetDB( DB_SLAVE ); |
979 | | - $options = ' ORDER BY smw_sortkey'; |
| 822 | + // the query needs to do the filtering of internal properties, else LIMIT is wrong |
| 823 | + $queries = array(); |
| 824 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 825 | + if ($proptable->fixedproperty == false) { |
| 826 | + $queries[] = 'SELECT smw_id, smw_title, COUNT(*) as count, smw_sortkey FROM ' . |
| 827 | + $db->tableName($proptable->name) . ' INNER JOIN ' . $db->tableName('smw_ids') . ' ON p_id=smw_id WHERE smw_iw=' . |
| 828 | + $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWPREDEFIW) . ' GROUP BY smw_id,smw_title,smw_sortkey'; |
| 829 | + } // else: properties with special tables are ignored for now; maybe fix in the future |
| 830 | + } |
| 831 | + $query = $db->unionQueries($queries, false) . ' ORDER BY smw_sortkey'; // should probably use $db->makeSelectOptions() |
980 | 832 | if ($requestoptions->limit > 0) { |
981 | | - $options .= ' LIMIT ' . $requestoptions->limit; |
| 833 | + $query = $db->limitResult($query,$requestoptions->limit,($requestoptions->offset > 0 )?$requestoptions->offset:0); |
982 | 834 | } |
983 | | - if ($requestoptions->offset > 0) { |
984 | | - $options .= ' OFFSET ' . $requestoptions->offset; |
985 | | - } |
986 | | - // NOTE: the query needs to do the filtering of internal properties, else LIMIT is wrong |
987 | | - $res = $db->query('(SELECT smw_id, smw_title, COUNT(*) as count, smw_sortkey FROM ' . |
988 | | - $db->tableName('smw_rels2') . ' INNER JOIN ' . $db->tableName('smw_ids') . ' ON p_id=smw_id WHERE smw_iw=' . |
989 | | - $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWPREDEFIW) . ' GROUP BY smw_id,smw_title,smw_sortkey) UNION ' . |
990 | | - '(SELECT smw_id, smw_title, COUNT(*) as count, smw_sortkey FROM ' . |
991 | | - $db->tableName('smw_spec2') . ' INNER JOIN ' . $db->tableName('smw_ids') . ' ON p_id=smw_id WHERE smw_iw=' . |
992 | | - $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWPREDEFIW) . ' GROUP BY smw_id,smw_title,smw_sortkey) UNION ' . |
993 | | - '(SELECT smw_id, smw_title, COUNT(*) as count, smw_sortkey FROM ' . |
994 | | - $db->tableName('smw_atts2') . ' INNER JOIN ' . $db->tableName('smw_ids') . ' ON p_id=smw_id WHERE smw_iw=' . |
995 | | - $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWPREDEFIW) . ' GROUP BY smw_id,smw_title,smw_sortkey) UNION ' . |
996 | | - '(SELECT smw_id, smw_title, COUNT(*) as count, smw_sortkey FROM ' . |
997 | | - $db->tableName('smw_text2') . ' INNER JOIN ' . $db->tableName('smw_ids') . ' ON p_id=smw_id WHERE smw_iw=' . |
998 | | - $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWPREDEFIW) . ' GROUP BY smw_id,smw_title,smw_sortkey) ' . $options, |
999 | | - 'SMW::getPropertySubjects'); |
| 835 | + $res = $db->query($query,'SMW::getPropertySubjects'); |
1000 | 836 | $result = array(); |
1001 | 837 | while($row = $db->fetchObject($res)) { |
1002 | 838 | $result[] = array(SMWPropertyValue::makeProperty($row->smw_title), $row->count); |
— | — | @@ -1005,21 +841,21 @@ |
1006 | 842 | return $result; |
1007 | 843 | } |
1008 | 844 | |
1009 | | - function getUnusedPropertiesSpecial($requestoptions = null) { |
| 845 | + /** |
| 846 | + * Implementation of SMWStore::getUnusedPropertiesSpecial(). It works by |
| 847 | + * creating a temporary table with all property pages from which all used |
| 848 | + * properties are then deleted. This is still a costy operation, and some |
| 849 | + * slower but lessdemanding way of getting at this data is required for |
| 850 | + * larger wikis. |
| 851 | + */ |
| 852 | + public function getUnusedPropertiesSpecial($requestoptions = null) { |
1010 | 853 | global $wgDBtype; |
1011 | 854 | wfProfileIn("SMWSQLStore2::getUnusedPropertiesSpecial (SMW)"); |
1012 | 855 | $db =& wfGetDB( DB_SLAVE ); |
1013 | | - /// TODO: some db-calls in here can use better wrapper functions, |
1014 | | - /// make an options array for those and use them |
1015 | | - $options = ' ORDER BY title'; |
1016 | | - if ($requestoptions->limit > 0) { |
1017 | | - $options .= ' LIMIT ' . $requestoptions->limit; |
1018 | | - } |
1019 | | - if ($requestoptions->offset > 0) { |
1020 | | - $options .= ' OFFSET ' . $requestoptions->offset; |
1021 | | - } |
1022 | | - extract( $db->tableNames('page', 'smw_rels2', 'smw_atts2', 'smw_text2', 'smw_subp2', 'smw_ids', 'smw_tmp_unusedprops', 'smw_redi2') ); |
| 856 | + $fname = 'SMW::getUnusedPropertySubjects'; |
1023 | 857 | |
| 858 | + // we use a temporary table for executing this costly operation on the DB side |
| 859 | + $smw_tmp_unusedprops = $db->tableName('smw_tmp_unusedprops'); |
1024 | 860 | if ($wgDBtype=='postgres') { // PostgresQL: no in-memory tables available |
1025 | 861 | $sql = "CREATE OR REPLACE FUNCTION create_" . $smw_tmp_unusedprops . "() RETURNS void AS " |
1026 | 862 | ."$$ " |
— | — | @@ -1036,217 +872,239 @@ |
1037 | 873 | } else { // MySQL: use temporary in-memory table |
1038 | 874 | $sql = "CREATE TEMPORARY TABLE " . $smw_tmp_unusedprops . "( title VARCHAR(255) ) TYPE=MEMORY"; |
1039 | 875 | } |
1040 | | - $db->query($sql, 'SMW::getUnusedPropertiesSpecial'); |
1041 | | - $db->query( "INSERT INTO $smw_tmp_unusedprops SELECT page_title FROM $page" . |
1042 | | - " WHERE page_namespace=" . SMW_NS_PROPERTY , 'SMW::getUnusedPropertySubjects'); |
1043 | | - foreach (array($smw_rels2,$smw_atts2,$smw_text2) as $table) { |
1044 | | - $db->query( "DELETE $smw_tmp_unusedprops.* FROM $smw_tmp_unusedprops, $table INNER JOIN $smw_ids ON p_id=smw_id WHERE title=smw_title AND smw_iw=" . $db->addQuotes(''), 'SMW::getUnusedPropertySubjects'); |
| 876 | + $db->query($sql, $fname); |
| 877 | + |
| 878 | + $db->insertSelect($smw_tmp_unusedprops, 'page', array('title' => 'page_title'), |
| 879 | + array("page_namespace" => SMW_NS_PROPERTY), $fname); |
| 880 | + |
| 881 | + $smw_ids = $db->tableName('smw_ids'); |
| 882 | + // all predefined properties are assumed to be used: |
| 883 | + $db->deleteJoin( $smw_tmp_unusedprops, $smw_ids, 'title', 'smw_title', array('smw_iw' => SMW_SQL2_SMWPREDEFIW), $fname ); |
| 884 | + // all tables occurring in some property table are used: |
| 885 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 886 | + if ($proptable->fixedproperty == false) { // MW does not seem to have a suitable wrapper for this |
| 887 | + $db->query( "DELETE $smw_tmp_unusedprops.* FROM $smw_tmp_unusedprops, " . $db->tableName($proptable->name) . |
| 888 | + " INNER JOIN $smw_ids ON p_id=smw_id WHERE title=smw_title AND smw_iw=" . $db->addQuotes(''), $fname); |
| 889 | + } // else: todo |
1045 | 890 | } |
1046 | | - $db->query( "DELETE $smw_tmp_unusedprops.* FROM $smw_tmp_unusedprops, $smw_subp2 INNER JOIN $smw_ids ON o_id=smw_id WHERE title=smw_title", 'SMW::getUnusedPropertySubjects'); |
1047 | | - $db->query( "DELETE $smw_tmp_unusedprops.* FROM $smw_tmp_unusedprops, $smw_ids WHERE title=smw_title AND smw_namespace=" . $db->addQuotes(SMW_NS_PROPERTY) . ' AND smw_iw=' . $db->addQuotes(SMW_SQL2_SMWPREDEFIW), 'SMW::getUnusedPropertySubjects'); |
1048 | | - // assume any property redirecting to some property to be used here: |
1049 | | - // (a stricter and more costy approach would be to delete only redirects to active properties; |
1050 | | - // this would need to be done with an addtional query in the above loop) |
1051 | | - $db->query( "DELETE $smw_tmp_unusedprops.* FROM $smw_tmp_unusedprops, $smw_redi2 INNER JOIN $smw_ids ON (s_title=smw_title AND s_namespace=" . $db->addQuotes(SMW_NS_PROPERTY) . ") WHERE title=smw_title", 'SMW::getUnusedPropertySubjects'); |
1052 | | - $res = $db->query("SELECT title FROM $smw_tmp_unusedprops " . $options, 'SMW::getUnusedPropertySubjects'); |
| 891 | + // properties that have subproperties are considered to be used |
| 892 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 893 | + $subtable = $proptables[SMWSQLStore2::findTypeTableID('__sup')]; // find the subproperty table, but consider its signature to be known |
| 894 | + // (again we have no fitting MW wrapper here:) |
| 895 | + $db->query( "DELETE $smw_tmp_unusedprops.* FROM $smw_tmp_unusedprops," . $db->tableName($subtable->name) . |
| 896 | + " INNER JOIN $smw_ids ON o_id=smw_id WHERE title=smw_title", $fname); |
| 897 | + // properties that are redirects are considered to be used: |
| 898 | + // (a stricter and more costy approach would be to delete only redirects to used properties; |
| 899 | + // this would need to be done with an addtional query in the above loop) |
| 900 | + // The redirect table is a fixed part of this store, no need to find its name. |
| 901 | + $db->deleteJoin( $smw_tmp_unusedprops, 'smw_redi2', 'title', 's_title', array('s_namespace' => SMW_NS_PROPERTY), $fname ); |
1053 | 902 | |
| 903 | + $options = $this->getSQLOptions($requestoptions,'title'); |
| 904 | + $options['ORDER BY'] = 'title'; |
| 905 | + $res = $db->select($smw_tmp_unusedprops, 'title', '', $fname, $options); |
1054 | 906 | $result = array(); |
1055 | 907 | while($row = $db->fetchObject($res)) { |
1056 | 908 | $result[] = SMWPropertyValue::makeProperty($row->title); |
1057 | 909 | } |
1058 | 910 | $db->freeResult($res); |
1059 | | - $db->query("DROP TEMPORARY table $smw_tmp_unusedprops", 'SMW::getUnusedPropertySubjects'); |
| 911 | + |
| 912 | + $db->query("DROP TEMPORARY table $smw_tmp_unusedprops", $fname); |
1060 | 913 | wfProfileOut("SMWSQLStore2::getUnusedPropertiesSpecial (SMW)"); |
1061 | 914 | return $result; |
1062 | 915 | } |
1063 | 916 | |
1064 | | - function getWantedPropertiesSpecial($requestoptions = null) { |
| 917 | + /** |
| 918 | + * Implementation of SMWStore::getWantedPropertiesSpecial(). Like all |
| 919 | + * WantedFoo specials, this function is very resource intensive and needs |
| 920 | + * to be cached on medium/large wikis. |
| 921 | + */ |
| 922 | + public function getWantedPropertiesSpecial($requestoptions = null) { |
1065 | 923 | global $smwgPDefaultType; |
1066 | 924 | wfProfileIn("SMWSQLStore2::getWantedPropertiesSpecial (SMW)"); |
1067 | | - switch (SMWSQLStore2::getStorageMode($smwgPDefaultType)) { |
1068 | | - case SMW_SQL2_RELS2: $table = 'smw_rels2'; break; |
1069 | | - case SMW_SQL2_ATTS2: $table = 'smw_atts2'; break; |
1070 | | - case SMW_SQL2_TEXT2: $table = 'smw_text2'; break; |
1071 | | - default: // nothing else is plausible enough to justify more lines |
1072 | | - wfProfileOut("SMWSQLStore2::getWantedPropertiesSpecial (SMW)"); |
1073 | | - return array(); |
1074 | | - } |
1075 | | - $db =& wfGetDB( DB_SLAVE ); |
1076 | | - $options = ' ORDER BY count DESC'; |
1077 | | - if ($requestoptions->limit > 0) { |
1078 | | - $options .= ' LIMIT ' . $requestoptions->limit; |
1079 | | - } |
1080 | | - if ($requestoptions->offset > 0) { |
1081 | | - $options .= ' OFFSET ' . $requestoptions->offset; |
1082 | | - } |
1083 | | - $res = $db->query('SELECT smw_title, COUNT(*) as count FROM ' . |
1084 | | - $db->tableName($table) . ' INNER JOIN ' . $db->tableName('smw_ids') . |
1085 | | - ' ON p_id=smw_id LEFT JOIN ' . $db->tableName('page') . |
1086 | | - ' ON (page_namespace=' . SMW_NS_PROPERTY . |
1087 | | - ' AND page_title=smw_title) WHERE smw_id > 50 AND page_id IS NULL GROUP BY smw_title' . $options, |
1088 | | - 'SMW::getWantedPropertiesSpecial'); |
| 925 | + // Note that Wanted Properties must have the default type. |
| 926 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 927 | + $proptable = $proptables[SMWSQLStore2::findTypeTableID($smwgPDefaultType)]; |
1089 | 928 | $result = array(); |
1090 | | - while($row = $db->fetchObject($res)) { |
1091 | | - $result[] = array(SMWPropertyValue::makeProperty($row->smw_title), $row->count); |
| 929 | + if ($proptable->fixedproperty == false) { // anything else would be crazy, but let's fail gracefully even if the whole world is crazy |
| 930 | + $db =& wfGetDB( DB_SLAVE ); |
| 931 | + $options = $this->getSQLOptions($requestoptions,'title'); |
| 932 | + $options['ORDER BY'] = 'count DESC'; |
| 933 | + $res = $db->select($db->tableName($proptable->name) . ' INNER JOIN ' . $db->tableName('smw_ids') . |
| 934 | + ' ON p_id=smw_id LEFT JOIN ' . $db->tableName('page') . ' ON (page_namespace=' . |
| 935 | + $db->addQuotes(SMW_NS_PROPERTY) . ' AND page_title=smw_title)', |
| 936 | + 'smw_title, COUNT(*) as count', 'smw_id > 50 AND page_id IS NULL GROUP BY smw_title', |
| 937 | + 'SMW::getWantedPropertiesSpecial', $options); |
| 938 | + while($row = $db->fetchObject($res)) { |
| 939 | + $result[] = array(SMWPropertyValue::makeProperty($row->smw_title), $row->count); |
| 940 | + } |
1092 | 941 | } |
1093 | 942 | wfProfileOut("SMWSQLStore2::getWantedPropertiesSpecial (SMW)"); |
1094 | 943 | return $result; |
1095 | 944 | } |
1096 | 945 | |
1097 | | - function getStatistics() { |
| 946 | + public function getStatistics() { |
1098 | 947 | wfProfileIn('SMWSQLStore2::getStatistics (SMW)'); |
1099 | 948 | $db =& wfGetDB( DB_SLAVE ); |
1100 | 949 | $result = array(); |
1101 | | - extract( $db->tableNames('smw_rels2', 'smw_atts2', 'smw_text2', 'smw_spec2') ); |
1102 | | - $propuses = 0; |
1103 | | - $usedprops = 0; |
1104 | | - foreach (array($smw_rels2, $smw_atts2, $smw_text2) as $table) { |
1105 | | - /// TODO: this currently counts parts of nary properties as singular property uses |
1106 | | - /// Is this minor issue worth the extra join of filtering those? |
1107 | | - $res = $db->query("SELECT COUNT(s_id) AS count FROM $table", 'SMW::getStatistics'); |
| 950 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 951 | + // count number of declared properties by counting "has type" annotations |
| 952 | + $typeprop = SMWPropertyValue::makeProperty('_TYPE'); |
| 953 | + $typetable = $proptables[SMWSQLStore2::findPropertyTableID($typeprop)]; |
| 954 | + $res = $db->select($typetable->name,'COUNT(s_id) AS count',array('p_id'=>$this->getSMWPropertyID($typeprop)),'SMW::getStatistics'); |
| 955 | + $row = $db->fetchObject( $res ); |
| 956 | + $result['DECLPROPS'] = $row->count; |
| 957 | + $db->freeResult( $res ); |
| 958 | + // count property uses by counting rows in property tables, |
| 959 | + // count used properties by counting distinct properties in each table |
| 960 | + $result['PROPUSES'] = 0; |
| 961 | + $result['USEDPROPS'] = 0; |
| 962 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 963 | + /// Note: subproperties that are part of container values are counted individually; |
| 964 | + /// It does not seem to be important to filter them by adding more conditions. |
| 965 | + $res = $db->select($proptable->name,'COUNT(*) AS count', '', 'SMW::getStatistics'); |
1108 | 966 | $row = $db->fetchObject( $res ); |
1109 | | - $propuses += $row->count; |
| 967 | + $result['PROPUSES'] += $row->count; |
1110 | 968 | $db->freeResult( $res ); |
1111 | | - $res = $db->query("SELECT COUNT(DISTINCT(p_id)) AS count FROM $table", 'SMW::getStatistics'); |
1112 | | - $row = $db->fetchObject( $res ); |
1113 | | - $usedprops += $row->count; |
| 969 | + if ($proptable->fixedproperty == false) { |
| 970 | + $res = $db->select($proptable->name,'COUNT(DISTINCT(p_id)) AS count', '', 'SMW::getStatistics'); |
| 971 | + $row = $db->fetchObject( $res ); |
| 972 | + $result['USEDPROPS'] += $row->count; |
| 973 | + } else { |
| 974 | + $res = $db->select($proptable->name,'*', '', 'SMW::getStatistics', array('LIMIT' => 1)); |
| 975 | + if ($db->numRows($res)>0) $result['USEDPROPS']++; |
| 976 | + } |
1114 | 977 | $db->freeResult( $res ); |
1115 | 978 | } |
1116 | | - $result['PROPUSES'] = $propuses; |
1117 | | - $result['USEDPROPS'] = $usedprops; |
1118 | 979 | |
1119 | | - $res = $db->query("SELECT COUNT(s_id) AS count FROM $smw_spec2 WHERE p_id=" . $db->addQuotes(SMWSQLStore2::$special_ids['_TYPE']), 'SMW::getStatistics'); |
1120 | | - $row = $db->fetchObject( $res ); |
1121 | | - $result['DECLPROPS'] = $row->count; |
1122 | | - $db->freeResult( $res ); |
1123 | | - |
1124 | 980 | wfProfileOut('SMWSQLStore2::getStatistics (SMW)'); |
1125 | 981 | return $result; |
1126 | 982 | } |
1127 | 983 | |
1128 | 984 | ///// Setup store ///// |
1129 | 985 | |
1130 | | - function setup($verbose = true) { |
1131 | | - global $wgDBtype; |
| 986 | + public function setup($verbose = true) { |
1132 | 987 | $this->reportProgress("Setting up standard database configuration for SMW ...\n\n",$verbose); |
1133 | 988 | $this->reportProgress("Selected storage engine is \"SMWSQLStore2\" (or an extension thereof)\n\n",$verbose); |
1134 | 989 | $db =& wfGetDB( DB_MASTER ); |
1135 | | - extract( $db->tableNames('smw_ids','smw_rels2','smw_atts2','smw_text2', |
1136 | | - 'smw_spec2','smw_subs2','smw_redi2','smw_inst2', |
1137 | | - 'smw_subp2','smw_conc2','smw_conccache') ); |
| 990 | + $this->setupTables($verbose, $db); |
| 991 | + $this->setupPredefinedProperties($verbose, $db); |
| 992 | + return true; |
| 993 | + } |
| 994 | + |
| 995 | + /** |
| 996 | + * Create required SQL tables. This function also performs upgrades of table contents |
| 997 | + * when required. |
| 998 | + * |
| 999 | + * Documentation for the table smw_ids: This table is normally used to store references to wiki |
| 1000 | + * pages (possibly with some external interwiki prefix). There are, however, some special objects |
| 1001 | + * that are also stored therein. These are marked by special interwiki prefixes (iw) that cannot |
| 1002 | + * occcur in real life: |
| 1003 | + * |
| 1004 | + * - Rows with iw SMW_SQL2_SMWIW describe "virtual" objects that have no page or other reference in the wiki. |
| 1005 | + * These are specifically the auxilliary objects ("bnodes") required to encode multi-valued properties, |
| 1006 | + * which are recognised by their empty title field. As a namespace, they use the id of the object that |
| 1007 | + * "owns" them, so that the can be reused/maintained more easily. |
| 1008 | + * A second object type that can occur in SMW_SQL2_SMWIW rows are the internal properties used to |
| 1009 | + * refer to some position in a multivalued property value. They have titles like "1", "2", "3", ... |
| 1010 | + * and occur only once (i.e. there is just one such property for the whoel wiki, and it has no type). |
| 1011 | + * The namespace of those entries is the usual property namespace. |
| 1012 | + * |
| 1013 | + * - Rows with iw SMW_SQL2_SMWREDIIW are similar to normal entries for (internal) wiki pages, but the iw |
| 1014 | + * indicates that the page is a redirect, the target of which should be sought using the smw_redi2 table. |
| 1015 | + * |
| 1016 | + * - The (unique) row with iw SMW_SQL2_SMWBORDERIW just marks the border between predefined ids (rows that |
| 1017 | + * are reserved for hardcoded ids built into SMW) and normal entries. It is no object, but makes sure that |
| 1018 | + * SQL's auto increment counter is high enough to not add any objects before that marked "border". |
| 1019 | + */ |
| 1020 | + protected function setupTables($verbose,$db) { |
| 1021 | + global $wgDBtype; |
| 1022 | + extract( $db->tableNames('smw_ids','smw_spec2','smw_conccache','smw_conc2') ); |
1138 | 1023 | $reportTo = $verbose?$this:null; // use $this to report back from static SMWSQLHelpers |
1139 | 1024 | // repeatedly used DB field types defined here for convenience |
1140 | | - $dbt_id = SMWSQLHelpers::getStandardDBType('id'); |
1141 | | - $dbt_namespace = SMWSQLHelpers::getStandardDBType('namespace'); |
1142 | | - $dbt_title = SMWSQLHelpers::getStandardDBType('title'); |
1143 | | - $dbt_iw = SMWSQLHelpers::getStandardDBType('iw'); |
1144 | | - $dbt_blob = SMWSQLHelpers::getStandardDBType('blob'); |
| 1025 | + $dbtypes = array('t'=>SMWSQLHelpers::getStandardDBType('title'), |
| 1026 | + 'u'=>($wgDBtype=='postgres'?'TEXT':'VARCHAR(63) binary'), |
| 1027 | + 'l'=>SMWSQLHelpers::getStandardDBType('blob'), |
| 1028 | + 'f'=>($wgDBtype=='postgres'?'DOUBLE PRECISION':'DOUBLE'), |
| 1029 | + 'i'=>($wgDBtype=='postgres'?'INTEGER':'INT(8)'), |
| 1030 | + 'j'=>($wgDBtype=='postgres'?'INTEGER':'INT(8) UNSIGNED'), |
| 1031 | + 'p'=>SMWSQLHelpers::getStandardDBType('id'), |
| 1032 | + 'n'=>SMWSQLHelpers::getStandardDBType('namespace'), |
| 1033 | + 'w'=>SMWSQLHelpers::getStandardDBType('iw')); |
1145 | 1034 | |
1146 | | - SMWSQLHelpers::setupTable($smw_ids, // internal IDs used in this store |
1147 | | - array('smw_id' => $dbt_id . ' NOT NULL' . ($wgDBtype=='postgres'?' PRIMARY KEY':' KEY AUTO_INCREMENT'), |
1148 | | - 'smw_namespace' => $dbt_namespace . ' NOT NULL', |
1149 | | - 'smw_title' => $dbt_title . ' NOT NULL', |
1150 | | - 'smw_iw' => $dbt_iw, |
1151 | | - 'smw_sortkey' => $dbt_title . ' NOT NULL'), $db, $reportTo); |
1152 | | - SMWSQLHelpers::setupIndex($smw_ids, array('smw_id','smw_title,smw_namespace,smw_iw', 'smw_sortkey'), $db); |
1153 | | - // NOTE: smw_ids is normally used to store references to wiki pages (possibly with some external |
1154 | | - // interwiki prefix). There are, however, some special objects that are also stored therein. These |
1155 | | - // are marked by special interwiki prefixes (iw) that cannot occcur in real life: |
1156 | | - // * Rows with iw SMW_SQL2_SMWIW describe "virtual" objects that have no page or other reference in the wiki. |
1157 | | - // These are specifically the auxilliary objects ("bnodes") required to encode multi-valued properties, |
1158 | | - // which are recognised by their empty title field. As a namespace, they use the id of the object that |
1159 | | - // "owns" them, so that the can be reused/maintained more easily. |
1160 | | - // A second object type that can occur in SMW_SQL2_SMWIW rows are the internal properties used to |
1161 | | - // refer to some position in a multivalued property value. They have titles like "1", "2", "3", ... |
1162 | | - // and occur only once (i.e. there is just one such property for the whoel wiki, and it has no type). |
1163 | | - // The namespace of those entries is the usual property namespace. |
1164 | | - // * Rows with iw SMW_SQL2_SMWREDIIW are similar to normal entries for (internal) wiki pages, but the iw |
1165 | | - // indicates that the page is a redirect, the target of which should be sought using the smw_redi2 table. |
1166 | | - // * The (unique) row with iw SMW_SQL2_SMWBORDERIW just marks the border between predefined ids (rows that |
1167 | | - // are reserved for hardcoded ids built into SMW) and normal entries. It is no object, but makes sure that |
1168 | | - // SQL's auto increment counter is high enough to not add any objects before that marked "border". |
1169 | | - |
1170 | | - SMWSQLHelpers::setupTable($smw_redi2, // fast redirect resolution |
1171 | | - array('s_title' => $dbt_title . ' NOT NULL', |
1172 | | - 's_namespace' => $dbt_namespace . ' NOT NULL', |
1173 | | - 'o_id' => $dbt_id . ' NOT NULL'), $db, $reportTo); |
1174 | | - SMWSQLHelpers::setupIndex($smw_redi2, array('s_title,s_namespace','o_id'), $db); |
1175 | | - |
1176 | | - SMWSQLHelpers::setupTable($smw_rels2, // properties with other pages as values ("relations") |
1177 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1178 | | - 'p_id' => $dbt_id . ' NOT NULL', |
1179 | | - 'o_id' => $dbt_id . ' NOT NULL'), $db, $reportTo); |
1180 | | - SMWSQLHelpers::setupIndex($smw_rels2, array('s_id','p_id','o_id'), $db); |
1181 | | - |
1182 | | - SMWSQLHelpers::setupTable($smw_atts2, // most standard properties ("attributes") |
1183 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1184 | | - 'p_id' => $dbt_id . ' NOT NULL', |
1185 | | - 'value_unit' => ($wgDBtype=='postgres'?'TEXT':'VARCHAR(63) binary'), |
1186 | | - 'value_xsd' => $dbt_title . ' NOT NULL', |
1187 | | - 'value_num' => ($wgDBtype=='postgres'?'DOUBLE PRECISION':'DOUBLE')), $db, $reportTo); |
1188 | | - SMWSQLHelpers::setupIndex($smw_atts2, array('s_id','p_id','value_num','value_xsd'), $db); |
1189 | | - |
1190 | | - SMWSQLHelpers::setupTable($smw_text2, // properties with long strings as values |
1191 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1192 | | - 'p_id' => $dbt_id . ' NOT NULL', |
1193 | | - 'value_blob' => $dbt_blob), $db, $reportTo); |
1194 | | - SMWSQLHelpers::setupIndex($smw_text2, array('s_id','p_id'), $db); |
1195 | | - |
1196 | | - // field renaming between SMW 1.3 and SMW 1.4: |
| 1035 | + // DB update: field renaming between SMW 1.3 and SMW 1.4 |
1197 | 1036 | if ( ($db->tableExists($smw_spec2)) && ($db->fieldExists($smw_spec2, 'sp_id', 'SMWSQLStore2::setup')) ) { |
1198 | 1037 | if ($wgDBtype=='postgres') { |
1199 | 1038 | $db->query("ALTER TABLE $smw_spec2 ALTER COLUMN sp_id RENAME TO p_id", 'SMWSQLStore2::setup'); |
1200 | 1039 | } else { |
1201 | | - $db->query("ALTER TABLE $smw_spec2 CHANGE `sp_id` `p_id` $dbt_id NOT NULL", 'SMWSQLStore2::setup'); |
| 1040 | + $db->query("ALTER TABLE $smw_spec2 CHANGE `sp_id` `p_id` " . $dbtypes['p'] . " NOT NULL", 'SMWSQLStore2::setup'); |
1202 | 1041 | } |
1203 | 1042 | } |
1204 | | - SMWSQLHelpers::setupTable($smw_spec2, // very important special properties, for faster access |
1205 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1206 | | - 'p_id' => $dbt_id . ' NOT NULL', |
1207 | | - 'value_string' => $dbt_title . ' NOT NULL'), $db, $reportTo); |
1208 | | - SMWSQLHelpers::setupIndex($smw_spec2, array('s_id', 'p_id', 's_id,p_id'), $db); |
1209 | 1043 | |
1210 | | - SMWSQLHelpers::setupTable($smw_subs2, // subclass relationships |
1211 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1212 | | - 'o_id' => $dbt_id . ' NOT NULL'), $db, $reportTo); |
1213 | | - SMWSQLHelpers::setupIndex($smw_subs2, array('s_id', 'o_id'), $db); |
| 1044 | + // set up table for internal IDs used in this store: |
| 1045 | + SMWSQLHelpers::setupTable($smw_ids, |
| 1046 | + array('smw_id' => $dbtypes['p'] . ' NOT NULL' . ($wgDBtype=='postgres'?' PRIMARY KEY':' KEY AUTO_INCREMENT'), |
| 1047 | + 'smw_namespace' => $dbtypes['n'] . ' NOT NULL', |
| 1048 | + 'smw_title' => $dbtypes['t'] . ' NOT NULL', |
| 1049 | + 'smw_iw' => $dbtypes['w'], |
| 1050 | + 'smw_sortkey' => $dbtypes['t'] . ' NOT NULL'), $db, $reportTo); |
| 1051 | + SMWSQLHelpers::setupIndex($smw_ids, array('smw_id','smw_title,smw_namespace,smw_iw', 'smw_sortkey'), $db); |
1214 | 1052 | |
1215 | | - SMWSQLHelpers::setupTable($smw_subp2, // subproperty relationships |
1216 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1217 | | - 'o_id' => $dbt_id . ' NOT NULL'), $db, $reportTo); |
1218 | | - SMWSQLHelpers::setupIndex($smw_subp2, array('s_id', 'o_id'), $db); |
1219 | | - |
1220 | | - SMWSQLHelpers::setupTable($smw_inst2, // class instances (s_id the element, o_id the class) |
1221 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1222 | | - 'o_id' => $dbt_id . ' NOT NULL',), $db, $reportTo); |
1223 | | - SMWSQLHelpers::setupIndex($smw_inst2, array('s_id', 'o_id'), $db); |
1224 | | - |
1225 | | - SMWSQLHelpers::setupTable($smw_conc2, // concept descriptions |
1226 | | - array('s_id' => $dbt_id . ' NOT NULL' . ($wgDBtype=='postgres'?' PRIMARY KEY':' KEY'), |
1227 | | - 'concept_txt' => $dbt_blob, |
1228 | | - 'concept_docu' => $dbt_blob, |
1229 | | - 'concept_features' => ($wgDBtype=='postgres'?'INTEGER':'INT(8)'), |
1230 | | - 'concept_size' => ($wgDBtype=='postgres'?'INTEGER':'INT(8)'), |
1231 | | - 'concept_depth' => ($wgDBtype=='postgres'?'INTEGER':'INT(8)'), |
1232 | | - 'cache_date' => ($wgDBtype=='postgres'?'INTEGER':'INT(8) UNSIGNED'), |
1233 | | - 'cache_count' => ($wgDBtype=='postgres'?'INTEGER':'INT(8) UNSIGNED'), ), $db, $reportTo); |
| 1053 | + // set up concept cache: member elements (s)->concepts (o) |
| 1054 | + SMWSQLHelpers::setupTable($smw_conccache, |
| 1055 | + array('s_id' => $dbtypes['p'] . ' NOT NULL', |
| 1056 | + 'o_id' => $dbtypes['p'] . ' NOT NULL'), $db, $reportTo); |
| 1057 | + SMWSQLHelpers::setupIndex($smw_conccache, array('o_id'), $db); |
| 1058 | + // set up concept descriptions |
| 1059 | + SMWSQLHelpers::setupTable($smw_conc2, |
| 1060 | + array('s_id' => $dbtypes['p'] . ' NOT NULL' . |
| 1061 | + ($wgDBtype=='postgres'?' PRIMARY KEY':' KEY'), |
| 1062 | + 'concept_txt' => $dbtypes['l'], |
| 1063 | + 'concept_docu' => $dbtypes['l'], |
| 1064 | + 'concept_features' => $dbtypes['i'], |
| 1065 | + 'concept_size' => $dbtypes['i'], |
| 1066 | + 'concept_depth' => $dbtypes['i'], |
| 1067 | + 'cache_date' => $dbtypes['j'], |
| 1068 | + 'cache_count' => $dbtypes['j'] ), $db, $reportTo); |
1234 | 1069 | SMWSQLHelpers::setupIndex($smw_conc2, array('s_id'), $db); |
1235 | 1070 | |
1236 | | - SMWSQLHelpers::setupTable($smw_conccache, // concept cache: member elements (s)->concepts (o) |
1237 | | - array('s_id' => $dbt_id . ' NOT NULL', |
1238 | | - 'o_id' => $dbt_id . ' NOT NULL'), $db, $reportTo); |
1239 | | - SMWSQLHelpers::setupIndex($smw_conccache, array('o_id'), $db); |
| 1071 | + // Set up all property tables as defined: |
| 1072 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 1073 | + if ($proptable->idsubject) { |
| 1074 | + $fieldarray = array('s_id' => $dbtypes['p'] . ' NOT NULL'); |
| 1075 | + $indexes = array('s_id'); |
| 1076 | + } else { |
| 1077 | + $fieldarray = array('s_title' => $dbtypes['t'] . ' NOT NULL', 's_namespace' => $dbtypes['n'] . ' NOT NULL'); |
| 1078 | + $indexes = array('s_title,s_namespace'); |
| 1079 | + } |
| 1080 | + if ($proptable->fixedproperty == false) { |
| 1081 | + $fieldarray['p_id'] = $dbtypes['p'] . ' NOT NULL'; |
| 1082 | + $indexes[] = 'p_id'; |
| 1083 | + } |
| 1084 | + foreach ($proptable->objectfields as $fieldname => $typeid) { |
| 1085 | + $fieldarray[$fieldname] = $dbtypes[$typeid]; |
| 1086 | + } |
| 1087 | + $indexes = array_merge($indexes, $proptable->indexes); |
| 1088 | + SMWSQLHelpers::setupTable($db->tableName($proptable->name), $fieldarray, $db, $reportTo); |
| 1089 | + SMWSQLHelpers::setupIndex($db->tableName($proptable->name), $indexes, $db); |
| 1090 | + } |
1240 | 1091 | |
1241 | 1092 | $this->reportProgress("Database initialised successfully.\n\n",$verbose); |
| 1093 | + } |
| 1094 | + |
| 1095 | + /** |
| 1096 | + * Create some initial DB entries for important built-in properties. Having the DB contents predefined |
| 1097 | + * allows us to safe DB calls when certain data is needed. At the same time, the entries in the DB |
| 1098 | + * make sure that DB-based functions work as with all other properties. |
| 1099 | + */ |
| 1100 | + protected function setupPredefinedProperties($verbose, $db) { |
| 1101 | + global $wgDBType; |
1242 | 1102 | $this->reportProgress("Setting up internal property indices ...\n",$verbose); |
1243 | 1103 | // Check if we already have this structure |
1244 | | - $borderiw = $db->selectField($smw_ids, 'smw_iw', 'smw_id=' . $db->addQuotes(50)); |
| 1104 | + $borderiw = $db->selectField('smw_ids', 'smw_iw', 'smw_id=' . $db->addQuotes(50)); |
1245 | 1105 | if ($borderiw != SMW_SQL2_SMWBORDERIW) { |
1246 | 1106 | $this->reportProgress(" ... allocate space for internal properties\n",$verbose); |
1247 | | - $this->moveID(50); // make sure position 50 is empty |
1248 | | - $db->insert( |
1249 | | - 'smw_ids', |
1250 | | - array( |
| 1107 | + $this->moveSMWPageID(50); // make sure position 50 is empty |
| 1108 | + $db->insert( 'smw_ids', array( |
1251 | 1109 | 'smw_id' => 50, |
1252 | 1110 | 'smw_title' => '', |
1253 | 1111 | 'smw_namespace' => 0, |
— | — | @@ -1257,8 +1115,8 @@ |
1258 | 1116 | |
1259 | 1117 | $this->reportProgress(" ",$verbose); |
1260 | 1118 | for ( $i=0; $i<50; $i++ ) { // make way for built-in ids |
1261 | | - $this->moveID( $i ); |
1262 | | - $this->reportProgress( ".", $verbose ); |
| 1119 | + $this->moveSMWPageID($i); |
| 1120 | + $this->reportProgress(".", $verbose); |
1263 | 1121 | } |
1264 | 1122 | $this->reportProgress("done\n",$verbose); |
1265 | 1123 | } else { |
— | — | @@ -1268,17 +1126,13 @@ |
1269 | 1127 | $this->reportProgress(" ... writing entries for internal properties.\n",$verbose); |
1270 | 1128 | foreach ( SMWSQLStore2::$special_ids as $prop => $id ) { |
1271 | 1129 | $p = SMWPropertyValue::makeProperty($prop); |
1272 | | - $db->replace( |
1273 | | - 'smw_ids', |
1274 | | - array( 'smw_id' ), |
1275 | | - array( |
| 1130 | + $db->replace( 'smw_ids', array( 'smw_id' ), array( |
1276 | 1131 | 'smw_id' => $id, |
1277 | 1132 | 'smw_title' => $p->getDBkey(), |
1278 | 1133 | 'smw_namespace' => SMW_NS_PROPERTY, |
1279 | 1134 | 'smw_iw' => $this->getPropertyInterwiki( $p ), |
1280 | 1135 | 'smw_sortkey' => $p->getDBkey() |
1281 | | - ), |
1282 | | - 'SMW::setup' |
| 1136 | + ), 'SMW::setup' |
1283 | 1137 | ); |
1284 | 1138 | } |
1285 | 1139 | if( $wgDBtype == 'postgres' ) { |
— | — | @@ -1288,16 +1142,16 @@ |
1289 | 1143 | $db->query( "ALTER SEQUENCE smw_ids_smw_id_seq RESTART WITH {$max}", __METHOD__ ); |
1290 | 1144 | } |
1291 | 1145 | $this->reportProgress("Internal properties initialised successfully.\n",$verbose); |
1292 | | - return true; |
1293 | 1146 | } |
1294 | 1147 | |
1295 | | - function drop($verbose = true) { |
| 1148 | + public function drop($verbose = true) { |
1296 | 1149 | global $wgDBtype; |
1297 | 1150 | $this->reportProgress("Deleting all database content and tables generated by SMW ...\n\n",$verbose); |
1298 | 1151 | $db =& wfGetDB( DB_MASTER ); |
1299 | | - $tables = array('smw_rels2', 'smw_atts2', 'smw_text2', 'smw_spec2', |
1300 | | - 'smw_subs2', 'smw_redi2', 'smw_ids', 'smw_inst2', |
1301 | | - 'smw_subp2', 'smw_conc2', 'smw_conccache'); |
| 1152 | + $tables = array('smw_ids', 'smw_conc2', 'smw_conccache'); |
| 1153 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 1154 | + $tables[] = $proptable->name; |
| 1155 | + } |
1302 | 1156 | foreach ($tables as $table) { |
1303 | 1157 | $name = $db->tableName($table); |
1304 | 1158 | $db->query('DROP TABLE' . ($wgDBtype=='postgres'?'':' IF EXISTS'). $name, 'SMWSQLStore2::drop'); |
— | — | @@ -1441,7 +1295,7 @@ |
1442 | 1296 | * The parameter $valuecol defines the string name of the column to which |
1443 | 1297 | * sorting requests etc. are to be applied. |
1444 | 1298 | */ |
1445 | | - protected function getSQLOptions($requestoptions, $valuecol = null) { |
| 1299 | + protected function getSQLOptions($requestoptions, $valuecol = '') { |
1446 | 1300 | $sql_options = array(); |
1447 | 1301 | if ($requestoptions !== null) { |
1448 | 1302 | if ($requestoptions->limit > 0) { |
— | — | @@ -1450,7 +1304,7 @@ |
1451 | 1305 | if ($requestoptions->offset > 0) { |
1452 | 1306 | $sql_options['OFFSET'] = $requestoptions->offset; |
1453 | 1307 | } |
1454 | | - if ( ($valuecol !== null) && ($requestoptions->sort) ) { |
| 1308 | + if ( ($valuecol != '') && ($requestoptions->sort) ) { |
1455 | 1309 | $sql_options['ORDER BY'] = $requestoptions->ascending ? $valuecol : $valuecol . ' DESC'; |
1456 | 1310 | } |
1457 | 1311 | } |
— | — | @@ -1464,20 +1318,21 @@ |
1465 | 1319 | * @param $requestoptions object with options |
1466 | 1320 | * @param $valuecol name of SQL column to which conditions apply |
1467 | 1321 | * @param $labelcol name of SQL column to which string conditions apply, if any |
| 1322 | + * @param $addand Boolean to indicate whether the string should begin with " AND " if non-empty |
1468 | 1323 | */ |
1469 | | - protected function getSQLConditions($requestoptions, $valuecol, $labelcol = null) { |
| 1324 | + protected function getSQLConditions($requestoptions, $valuecol = '', $labelcol = '', $addand = true) { |
1470 | 1325 | $sql_conds = ''; |
1471 | 1326 | if ($requestoptions !== null) { |
1472 | | - $db =& wfGetDB( DB_SLAVE ); |
1473 | | - if ($requestoptions->boundary !== null) { // apply value boundary |
| 1327 | + $db =& wfGetDB( DB_SLAVE ); /// TODO avoid doing this here again, all callers should have one |
| 1328 | + if ( ($valuecol != '') && ($requestoptions->boundary !== null) ) { // apply value boundary |
1474 | 1329 | if ($requestoptions->ascending) { |
1475 | 1330 | $op = $requestoptions->include_boundary?' >= ':' > '; |
1476 | 1331 | } else { |
1477 | 1332 | $op = $requestoptions->include_boundary?' <= ':' < '; |
1478 | 1333 | } |
1479 | | - $sql_conds .= ' AND ' . $valuecol . $op . $db->addQuotes($requestoptions->boundary); |
| 1334 | + $sql_conds .= ($addand?' AND ':'') . $valuecol . $op . $db->addQuotes($requestoptions->boundary); |
1480 | 1335 | } |
1481 | | - if ($labelcol !== null) { // apply string conditions |
| 1336 | + if ($labelcol != '') { // apply string conditions |
1482 | 1337 | foreach ($requestoptions->getStringConditions() as $strcond) { |
1483 | 1338 | $string = str_replace('_', '\_', $strcond->string); |
1484 | 1339 | switch ($strcond->condition) { |
— | — | @@ -1485,7 +1340,7 @@ |
1486 | 1341 | case SMWStringCondition::STRCOND_POST: $string = '%' . $string; break; |
1487 | 1342 | case SMWStringCondition::STRCOND_MID: $string = '%' . $string . '%'; break; |
1488 | 1343 | } |
1489 | | - $sql_conds .= ' AND ' . $labelcol . ' LIKE ' . $db->addQuotes($string); |
| 1344 | + $sql_conds .= (($addand || ($sql_conds!=''))?' AND ':'') . $labelcol . ' LIKE ' . $db->addQuotes($string); |
1490 | 1345 | } |
1491 | 1346 | } |
1492 | 1347 | } |
— | — | @@ -1493,39 +1348,29 @@ |
1494 | 1349 | } |
1495 | 1350 | |
1496 | 1351 | /** |
1497 | | - * Not in all cases can requestoptions be forwarded to the DB using getSQLConditions() |
1498 | | - * and getSQLOptions(): some data comes from caches that do not respect the options yet. |
1499 | | - * This method takes an array of results (SMWDataValue or Title objects) and applies |
1500 | | - * the given requestoptions as appropriate. |
| 1352 | + * Not in all cases can requestoptions be forwarded to the DB using |
| 1353 | + * getSQLConditions() and getSQLOptions(): some data comes from caches that |
| 1354 | + * do not respect the options yet. This method takes an array of results |
| 1355 | + * (SMWDataValue objects) *of the same type* and applies the given |
| 1356 | + * requestoptions as appropriate. |
1501 | 1357 | */ |
1502 | 1358 | protected function applyRequestOptions($data, $requestoptions) { |
1503 | 1359 | wfProfileIn("SMWSQLStore2::applyRequestOptions (SMW)"); |
1504 | | - $result = array(); |
1505 | | - $sortres = array(); |
1506 | | - $key = 0; |
1507 | 1360 | if ( (count($data) == 0) || ($requestoptions === null) ) { |
1508 | 1361 | wfProfileOut("SMWSQLStore2::applyRequestOptions (SMW)"); |
1509 | 1362 | return $data; |
1510 | 1363 | } |
| 1364 | + $result = array(); |
| 1365 | + $sortres = array(); |
| 1366 | + list($sig,$valueindex,$labelindex) = SMWSQLStore2::getTypeSignature(reset($data)->getTypeID()); |
| 1367 | + $numeric = ( ($valueindex>=0) && (strlen($sig)>$valueindex) && |
| 1368 | + ( ($sig{$valueindex} != 'f') || ($sig{$valueindex} != 'n') ) ); |
| 1369 | + $i = 0; |
1511 | 1370 | foreach ($data as $item) { |
1512 | | - $numeric = false; |
1513 | | - $ok = true; |
1514 | | - if ($item instanceof SMWWikiPageValue) { |
1515 | | - $label = $item->getSortkey(); |
1516 | | - $value = $label; |
1517 | | - } elseif ($item instanceof SMWDataValue) { |
1518 | | - $keys = $item->getDBkeys(); // use DB keys since we need to mimic the behaviour of direct SQL conditions (which also use the DB key) |
1519 | | - $label = $keys[0]; |
1520 | | - if ($item->isNumeric()) { |
1521 | | - $value = $item->getNumericValue(); |
1522 | | - $numeric = true; |
1523 | | - } else { |
1524 | | - $value = $label; |
1525 | | - } |
1526 | | - } else { // instance of Title |
1527 | | - $label = $item->getText(); /// NOTE: no prefixed text, since only Text is used in SQL operations |
1528 | | - $value = $label; |
1529 | | - } |
| 1371 | + $ok = true; // keep datavalue only if this remains true |
| 1372 | + $keys = $item->getDBkeys(); |
| 1373 | + $value = array_key_exists($valueindex,$keys)?$keys[$valueindex]:''; |
| 1374 | + $label = array_key_exists($labelindex,$keys)?$keys[$labelindex]:''; |
1530 | 1375 | if ($requestoptions->boundary !== null) { // apply value boundary |
1531 | 1376 | $strc = $numeric?0:strcmp($value,$requestoptions->boundary); |
1532 | 1377 | if ($requestoptions->ascending) { |
— | — | @@ -1556,13 +1401,12 @@ |
1557 | 1402 | } |
1558 | 1403 | } |
1559 | 1404 | if ($ok) { |
1560 | | - $result[$key] = $item; |
1561 | | - $sortres[$key] = $value; // we cannot use $value as key: it is not unique if there are units! |
1562 | | - $key++; |
| 1405 | + $result[$i] = $item; |
| 1406 | + $sortres[$i] = $value; // we cannot use $value as key: it is not unique if there are units! |
| 1407 | + $i++; |
1563 | 1408 | } |
1564 | 1409 | } |
1565 | 1410 | if ($requestoptions->sort) { |
1566 | | - // use last value of $numeric to indicate overall type |
1567 | 1411 | $flag = $numeric?SORT_NUMERIC:SORT_LOCALE_STRING; |
1568 | 1412 | if ($requestoptions->ascending) { |
1569 | 1413 | asort($sortres,$flag); |
— | — | @@ -1600,31 +1444,92 @@ |
1601 | 1445 | } |
1602 | 1446 | |
1603 | 1447 | /** |
1604 | | - * Retrieve a constant that defines how values of the given type should be stored. The constant refers |
1605 | | - * to the internal storage details of this class (which table, which mapping from datavalue features to |
1606 | | - * table cells, ...). |
| 1448 | + * For a given SMW type id, obtain the "signature" from which the |
| 1449 | + * appropriate property table and information about sorting/filtering |
| 1450 | + * data of this type can be obtained. The result is an array of three |
| 1451 | + * entries: a signature string, the index of the value field, and |
| 1452 | + * the index of the label label field. These entries correspond to |
| 1453 | + * the results of SMWDataValue::getSignature(), |
| 1454 | + * SMWDatavalue::getValueIndex(), and SMWDatavalue::getLabelIndex(). |
| 1455 | + * @todo Custom unit types (SMWLinearValue) have page names as their |
| 1456 | + * type id and are not in the array cache. Can we still determine their |
| 1457 | + * signature without creating them? |
1607 | 1458 | */ |
1608 | | - public static function getStorageMode($typeid) { |
1609 | | - if (array_key_exists($typeid, SMWSQLStore2::$storage_mode)) { |
1610 | | - return SMWSQLStore2::$storage_mode[$typeid]; |
1611 | | - } else { // maybe an extension type; make an effort to guess suitable storage mode |
| 1459 | + public static function getTypeSignature($typeid) { |
| 1460 | + if (!array_key_exists($typeid, SMWSQLStore2::$type_signatures)) { |
1612 | 1461 | $dv = SMWDataValueFactory::newTypeIDValue($typeid); |
1613 | | - ///TODO: More could be done here, this is just educated guessing; but better control of |
1614 | | - /// storage modes suggests to do a general rewrite of the store as a first step (based on |
1615 | | - /// data-level "table descriptions" instead of inline code) |
1616 | | - if ($dv instanceof SMWWikiPageValue) { |
1617 | | - return SMWSQLStore2::$storage_mode['_wpg']; |
1618 | | - } else { |
1619 | | - return SMW_SQL2_ATTS2; |
| 1462 | + SMWSQLStore2::$type_signatures[$typeid] = array($dv->getSignature(), $dv->getValueIndex(), $dv->getLabelIndex()); |
| 1463 | + } |
| 1464 | + return SMWSQLStore2::$type_signatures[$typeid]; |
| 1465 | + } |
| 1466 | + |
| 1467 | + /** |
| 1468 | + * Check if the given table can be used to store values of the given |
| 1469 | + * signature, where $signature is as returned by getTypeSignature(). |
| 1470 | + * @todo Maybe rather use SMWSQLStore2Table object as parameter. |
| 1471 | + */ |
| 1472 | + public static function tableFitsSignature($tableid, $signature) { |
| 1473 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 1474 | + $tablesig = str_replace('p','tnwt', $proptables[$tableid]->getFieldSignature()); // expand internal page type to single fields |
| 1475 | + $valuesig = reset($signature); |
| 1476 | + return ( $valuesig == substr($tablesig,0,strlen($valuesig)) ); |
| 1477 | + } |
| 1478 | + |
| 1479 | + /** |
| 1480 | + * Check if the given table can be used to store values of the given |
| 1481 | + * type. |
| 1482 | + */ |
| 1483 | + public static function tableFitsType($tableid, $typeid) { |
| 1484 | + return SMWSQLStore2::tableFitsSignature($tableid,SMWSQLStore2::getTypeSignature($typeid)); |
| 1485 | + } |
| 1486 | + |
| 1487 | + /** |
| 1488 | + * Find the id of a property table that is suitable for storing values of |
| 1489 | + * the given type. The type is specified by an SMW type id such as '_wpg'. |
| 1490 | + * An empty string is returned if no matching table could be found. |
| 1491 | + */ |
| 1492 | + public static function findTypeTableID($typeid) { |
| 1493 | + if (!array_key_exists($typeid,SMWSQLStore2::$property_table_ids)) { |
| 1494 | + $signature = SMWSQLStore2::getTypeSignature($typeid); |
| 1495 | + foreach (SMWSQLStore2::getPropertyTables() as $tid => $proptable) { |
| 1496 | + if (SMWSQLStore2::tableFitsSignature($tid,$signature)) { |
| 1497 | + SMWSQLStore2::$property_table_ids[$typeid] = $tid; |
| 1498 | + return $tid; |
| 1499 | + } |
1620 | 1500 | } |
| 1501 | + SMWSQLStore2::$property_table_ids[$typeid] = ''; // no matching table found |
1621 | 1502 | } |
| 1503 | + return SMWSQLStore2::$property_table_ids[$typeid]; |
1622 | 1504 | } |
1623 | 1505 | |
1624 | 1506 | /** |
| 1507 | + * Retrieve the id of the property table that is to be used for storing |
| 1508 | + * values for the given property object. |
| 1509 | + */ |
| 1510 | + public static function findPropertyTableID($property) { |
| 1511 | + if (SMWSQLStore2::$fixed_prop_tables === null) { // build lookup array once |
| 1512 | + SMWSQLStore2::$fixed_prop_tables = array(); |
| 1513 | + foreach (SMWSQLStore2::getPropertyTables() as $tid => $proptable) { |
| 1514 | + if ($proptable->fixedproperty != false) { |
| 1515 | + SMWSQLStore2::$fixed_prop_tables[$proptable->fixedproperty] = $tid; |
| 1516 | + } |
| 1517 | + } |
| 1518 | + } |
| 1519 | + $propertykey = ($property->isUserDefined())?$property->getDBkey():$property->getPropertyId(); |
| 1520 | + if (array_key_exists($propertykey,SMWSQLStore2::$fixed_prop_tables)) { |
| 1521 | + $signature = SMWSQLStore2::getTypeSignature($property->getPropertyTypeID()); |
| 1522 | + if (SMWSQLStore2::tableFitsSignature(SMWSQLStore2::$fixed_prop_tables[$propertykey],$signature)) |
| 1523 | + return SMWSQLStore2::$fixed_prop_tables[$propertykey]; |
| 1524 | + } // else: don't check for non-fitting entries in $fixed_prop_tables: not really important |
| 1525 | + |
| 1526 | + return SMWSQLStore2::findTypeTableID($property->getPropertyTypeID()); |
| 1527 | + } |
| 1528 | + |
| 1529 | + /** |
1625 | 1530 | * Find the numeric ID used for the page of the given title and namespace. |
1626 | | - * If $canonical is set to true, redirects are taken into account to find the |
1627 | | - * canonical alias ID for the given page. |
1628 | | - * If no such ID exists, 0 is returned. |
| 1531 | + * If $canonical is set to true, redirects are taken into account to find |
| 1532 | + * the canonical alias ID for the given page. If no such ID exists, 0 is |
| 1533 | + * returned. |
1629 | 1534 | */ |
1630 | 1535 | public function getSMWPageID($title, $namespace, $iw, $canonical=true) { |
1631 | 1536 | $sort = ''; |
— | — | @@ -1632,8 +1537,11 @@ |
1633 | 1538 | } |
1634 | 1539 | |
1635 | 1540 | /** |
1636 | | - * Like getSMWPageID, but also sets the Call-By-Ref parameter $sort to the current |
1637 | | - * sortkey. |
| 1541 | + * Like getSMWPageID(), but also sets the Call-By-Ref parameter $sort to |
| 1542 | + * the current sortkey. |
| 1543 | + * @todo Ensuring that properties redirect to properties only should not be done here. |
| 1544 | + * @todo Centralise creation of id cache keys, and make sure non-local pages have only one key |
| 1545 | + * (no need to distniguish canonical/non-canonical in this case). |
1638 | 1546 | */ |
1639 | 1547 | public function getSMWPageIDandSort($title, $namespace, $iw, &$sort, $canonical) { |
1640 | 1548 | global $smwgQEqualitySupport; |
— | — | @@ -1650,54 +1558,45 @@ |
1651 | 1559 | } |
1652 | 1560 | $db =& wfGetDB( DB_SLAVE ); |
1653 | 1561 | $id = 0; |
1654 | | - $redirect = false; |
1655 | | - if ($iw != '') { |
1656 | | - $res = $db->select('smw_ids', array('smw_id','smw_sortkey'), 'smw_title=' . $db->addQuotes($title) . ' AND ' . 'smw_namespace=' . $db->addQuotes($namespace) . ' AND smw_iw=' . $db->addQuotes($iw), 'SMW::getSMWPageID', array('LIMIT'=>1)); |
| 1562 | + if ($iw != '') { // external page; no need to think about redirects |
| 1563 | + $res = $db->select('smw_ids', array('smw_id','smw_sortkey'), |
| 1564 | + array('smw_title' => $title, 'smw_namespace' => $namespace, 'smw_iw'=>$iw), |
| 1565 | + 'SMW::getSMWPageID', array('LIMIT'=>1)); |
1657 | 1566 | if ($row = $db->fetchObject($res)) { |
1658 | 1567 | $id = $row->smw_id; |
1659 | 1568 | $sort = $row->smw_sortkey; |
1660 | 1569 | } |
| 1570 | + $this->m_ids[ $canonical?$nkey:$ckey ] = $id; // unique id, make sure cache for canonical+non-cacnonical gets filled |
1661 | 1571 | } else { // check for potential redirects also |
1662 | | - $res = $db->select('smw_ids', array('smw_id', 'smw_iw', 'smw_sortkey'), 'smw_title=' . $db->addQuotes($title) . ' AND ' . 'smw_namespace=' . $db->addQuotes($namespace) . ' AND (smw_iw=' . $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWREDIIW) . ')', 'SMW::getSMWPageID', array('LIMIT'=>1)); |
| 1572 | + $res = $db->select('smw_ids', array('smw_id', 'smw_iw', 'smw_sortkey'), |
| 1573 | + 'smw_title=' . $db->addQuotes($title) . ' AND smw_namespace=' . $db->addQuotes($namespace) . |
| 1574 | + ' AND (smw_iw=' . $db->addQuotes('') . ' OR smw_iw=' . $db->addQuotes(SMW_SQL2_SMWREDIIW) . ')', |
| 1575 | + 'SMW::getSMWPageID', array('LIMIT'=>1)); |
1663 | 1576 | if ($row = $db->fetchObject($res)) { |
1664 | | - $sort = $row->smw_sortkey; |
1665 | 1577 | $id = $row->smw_id; // set id in any case, the below check for properties will use even the redirect id in emergency |
1666 | | - if ( ($row->smw_iw == '') || (!$canonical) || ($smwgQEqualitySupport == SMW_EQ_NONE) ) { |
1667 | | - if ($row->smw_iw == '') { |
1668 | | - $this->m_ids[$ckey] = $id; // what we found is also the canonical key, cache it |
| 1578 | + $sort = $row->smw_sortkey; |
| 1579 | + if ( ($row->smw_iw == '') ) { // the id found is unique (canonical and non-canonical); fill cache also for the case *not* asked for |
| 1580 | + $this->m_ids[ $canonical?$nkey:$ckey ] = $id; // (the other cache is filled below) |
| 1581 | + } elseif ( $canonical && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { // get redirect alias |
| 1582 | + if ($namespace == SMW_NS_PROPERTY) { // redirect properties only to properties |
| 1583 | + ///TODO: Shouldn't this condition be ensured during writing? |
| 1584 | + $res2 = $db->select( array( 'smw_redi2', 'smw_ids' ), 'o_id', |
| 1585 | + 'o_id=smw_id AND smw_namespace=s_namespace AND s_title=' . $db->addQuotes($title) . |
| 1586 | + ' AND s_namespace=' . $db->addQuotes($namespace), 'SMW::getSMWPageID', array( 'LIMIT'=>1 ) ); |
| 1587 | + } else { |
| 1588 | + $res2 = $db->select( 'smw_redi2', 'o_id', |
| 1589 | + 's_title=' . $db->addQuotes($title) . ' AND s_namespace=' . $db->addQuotes($namespace), |
| 1590 | + 'SMW::getSMWPageID', array( 'LIMIT' => 1 ) ); |
1669 | 1591 | } |
1670 | | - } else { |
1671 | | - $redirect = true; |
1672 | | - $this->m_ids[$nkey] = $id; // what we found is the non-canonical key, cache it |
| 1592 | + if ($row = $db->fetchObject($res2)) { |
| 1593 | + $id = $row->o_id; |
| 1594 | + } |
| 1595 | + $db->freeResult($res2); |
1673 | 1596 | } |
1674 | 1597 | } |
1675 | 1598 | } |
1676 | 1599 | $db->freeResult($res); |
1677 | 1600 | |
1678 | | - if ($redirect) { // get redirect alias |
1679 | | - if ($namespace == SMW_NS_PROPERTY) { // redirect properties only to properties |
1680 | | - /// FIXME: Shouldn't this condition be ensured during writing? |
1681 | | - $res = $db->select( |
1682 | | - array( 'smw_redi2', 'smw_ids' ), |
1683 | | - 'o_id', |
1684 | | - 'o_id=smw_id AND smw_namespace=s_namespace AND s_title=' . $db->addQuotes($title) . ' AND s_namespace=' . $db->addQuotes($namespace), |
1685 | | - 'SMW::getSMWPageID', |
1686 | | - array( 'LIMIT'=>1 ) |
1687 | | - ); |
1688 | | - } else { |
1689 | | - $res = $db->select( |
1690 | | - 'smw_redi2', |
1691 | | - 'o_id', |
1692 | | - 's_title=' . $db->addQuotes($title) . ' AND s_namespace=' . $db->addQuotes($namespace), |
1693 | | - 'SMW::getSMWPageID', |
1694 | | - array( 'LIMIT' => 1 ) |
1695 | | - ); |
1696 | | - } |
1697 | | - if ($row = $db->fetchObject($res)) { |
1698 | | - $id = $row->o_id; |
1699 | | - } |
1700 | | - $db->freeResult($res); |
1701 | | - } |
1702 | 1601 | $this->m_ids[$key] = $id; |
1703 | 1602 | wfProfileOut('SMWSQLStore2::getSMWPageID (SMW)'); |
1704 | 1603 | return $id; |
— | — | @@ -1705,13 +1604,13 @@ |
1706 | 1605 | |
1707 | 1606 | /** |
1708 | 1607 | * Find the numeric ID used for the page of the given title and namespace. |
1709 | | - * If $canonical is set to true, redirects are taken into account to find the |
1710 | | - * canonical alias ID for the given page. |
1711 | | - * If no such ID exists, a new ID is created and returned. |
1712 | | - * In any case, the current sortkey is set to the given one unless $sortkey |
1713 | | - * is empty. |
1714 | | - * @note Using this with $canonical==false may make sense, especially when |
| 1608 | + * If $canonical is set to true, redirects are taken into account to find |
| 1609 | + * the canonical alias ID for the given page. If no such ID exists, a new |
| 1610 | + * ID is created and returned. In any case, the current sortkey is set to |
| 1611 | + * the given one unless $sortkey is empty. |
| 1612 | + * @note Using this with $canonical==false can make sense, especially when |
1715 | 1613 | * the title is a redirect target (we do not want chains of redirects). |
| 1614 | + * But it is of no relevance if the title does not have an id yet. |
1716 | 1615 | */ |
1717 | 1616 | protected function makeSMWPageID($title, $namespace, $iw, $canonical=true, $sortkey = '') { |
1718 | 1617 | wfProfileIn('SMWSQLStore2::makeSMWPageID (SMW)'); |
— | — | @@ -1721,14 +1620,11 @@ |
1722 | 1621 | $db =& wfGetDB( DB_MASTER ); |
1723 | 1622 | $sortkey = $sortkey?$sortkey:(str_replace('_',' ',$title)); |
1724 | 1623 | $db->insert('smw_ids', |
1725 | | - array( |
1726 | | - 'smw_id' => $db->nextSequenceValue('smw_ids_smw_id_seq'), |
1727 | | - 'smw_title' => $title, |
1728 | | - 'smw_namespace' => $namespace, |
1729 | | - 'smw_iw' => $iw, |
1730 | | - 'smw_sortkey' => $sortkey |
1731 | | - ), |
1732 | | - 'SMW::makeSMWPageID'); |
| 1624 | + array( 'smw_id' => $db->nextSequenceValue('smw_ids_smw_id_seq'), |
| 1625 | + 'smw_title' => $title, |
| 1626 | + 'smw_namespace' => $namespace, |
| 1627 | + 'smw_iw' => $iw, |
| 1628 | + 'smw_sortkey' => $sortkey ), 'SMW::makeSMWPageID'); |
1733 | 1629 | $id = $db->insertId(); |
1734 | 1630 | $this->m_ids["$iw $namespace $title -"] = $id; // fill that cache, even if canonical was given |
1735 | 1631 | // This ID is also authorative for the canonical version. |
— | — | @@ -1744,357 +1640,368 @@ |
1745 | 1641 | } |
1746 | 1642 | |
1747 | 1643 | /** |
1748 | | - * Properties have a mechanisms for being predefined (i.e. in PHP instead of in wiki). Special |
1749 | | - * "interwiki" prefixes are separate the ids of such predefined properties from the ids for the |
1750 | | - * current pages (which may, e.g. be moved, while the predefined object is not movable!). |
| 1644 | + * Properties have a mechanisms for being predefined (i.e. in PHP instead |
| 1645 | + * of in wiki). Special "interwiki" prefixes separate the ids of such |
| 1646 | + * predefined properties from the ids for the current pages (which may, |
| 1647 | + * e.g. be moved, while the predefined object is not movable). |
1751 | 1648 | */ |
1752 | 1649 | private function getPropertyInterwiki(SMWPropertyValue $property) { |
1753 | 1650 | if ($property->isUserDefined()) { |
1754 | 1651 | return ''; |
1755 | | - } elseif ($property->isVisible()) { |
1756 | | - return SMW_SQL2_SMWPREDEFIW; |
1757 | 1652 | } else { |
1758 | | - return SMW_SQL2_SMWINTDEFIW; |
| 1653 | + return $property->isVisible()?SMW_SQL2_SMWPREDEFIW:SMW_SQL2_SMWINTDEFIW; |
1759 | 1654 | } |
1760 | 1655 | } |
1761 | 1656 | |
1762 | 1657 | /** |
1763 | | - * Like getSMWPageID but taking into account that properties might be predefined. |
| 1658 | + * This function does the same as getSMWPageID() but takes into account |
| 1659 | + * that properties might be predefined. |
1764 | 1660 | */ |
1765 | 1661 | public function getSMWPropertyID(SMWPropertyValue $property) { |
1766 | | - if ( (!$property->isUserDefined()) && (array_key_exists($property->getPropertyID(), SMWSQLStore2::$special_ids))) { // very important property? |
1767 | | - return SMWSQLStore2::$special_ids[$property->getPropertyID()]; |
| 1662 | + if ( (!$property->isUserDefined()) && (array_key_exists($property->getPropertyID(), SMWSQLStore2::$special_ids))) { |
| 1663 | + return SMWSQLStore2::$special_ids[$property->getPropertyID()]; // very important property with fixed id |
1768 | 1664 | } else { |
1769 | 1665 | return $this->getSMWPageID($property->getDBkey(),SMW_NS_PROPERTY,$this->getPropertyInterwiki($property),true); |
1770 | 1666 | } |
1771 | 1667 | } |
1772 | 1668 | |
1773 | 1669 | /** |
1774 | | - * Like makeSMWPageID but taking into account that properties might be predefined. |
| 1670 | + * This function does the same as makeSMWPageID() but takes into account |
| 1671 | + * that properties might be predefined. |
1775 | 1672 | */ |
1776 | 1673 | protected function makeSMWPropertyID(SMWPropertyValue $property) { |
1777 | | - if ( (!$property->isUserDefined()) && (array_key_exists($property->getPropertyID(), SMWSQLStore2::$special_ids))) { // very important property? |
1778 | | - return SMWSQLStore2::$special_ids[$property->getPropertyID()]; |
| 1674 | + if ( (!$property->isUserDefined()) && (array_key_exists($property->getPropertyID(), SMWSQLStore2::$special_ids))) { |
| 1675 | + return SMWSQLStore2::$special_ids[$property->getPropertyID()]; // very important property with fixed id |
1779 | 1676 | } else { |
1780 | 1677 | return $this->makeSMWPageID($property->getDBkey(),SMW_NS_PROPERTY,$this->getPropertyInterwiki($property),true); |
1781 | 1678 | } |
1782 | 1679 | } |
1783 | 1680 | |
1784 | 1681 | /** |
1785 | | - * Extend the ID cache as specified. This is called in places where IDs are retrieved |
1786 | | - * by SQL queries and it would be a pity to throw them away. This function expects to |
1787 | | - * get the contents of a line in smw_ids, i.e. possibly with iw being SMW_SQL2_SMWREDIIW. |
1788 | | - * This information is used to determine whether the given ID is canonical or not. |
| 1682 | + * Extend the ID cache as specified. This is called in places where IDs are |
| 1683 | + * retrieved by SQL queries and it would be a pity to throw them away. This |
| 1684 | + * function expects to get the contents of a row in smw_ids, i.e. possibly |
| 1685 | + * with iw being SMW_SQL2_SMWREDIIW. This information is used to determine |
| 1686 | + * whether the given ID is canonical or not. |
1789 | 1687 | */ |
1790 | 1688 | public function cacheSMWPageID($id, $title, $namespace, $iw) { |
1791 | | - $real_iw = ($iw == SMW_SQL2_SMWREDIIW)?'':$iw; |
1792 | 1689 | $ckey = "$iw $namespace $title C"; |
1793 | 1690 | $nkey = "$iw $namespace $title -"; |
1794 | 1691 | if (count($this->m_ids)>1500) { // prevent memory leak in very long PHP runs |
1795 | 1692 | $this->m_ids = array(); |
1796 | 1693 | } |
1797 | 1694 | $this->m_ids[$nkey] = $id; |
1798 | | - if ($real_iw === $iw) { |
| 1695 | + if ($iw != SMW_SQL2_SMWREDIIW) { |
1799 | 1696 | $this->m_ids[$ckey] = $id; |
1800 | 1697 | } |
1801 | 1698 | } |
1802 | 1699 | |
1803 | 1700 | /** |
1804 | | - * Get a numeric ID for some Bnode that is to be used to encode an arbitrary |
1805 | | - * n-ary property. Bnodes are managed through the smw_ids table but will always |
1806 | | - * have an empty smw_title, and smw_namespace being set to the parent object |
1807 | | - * (the id of the page that uses the Bnode). Unused Bnodes are not deleted but |
1808 | | - * marked as available by setting smw_namespace to 0. This method then tries to |
1809 | | - * reuse an unused bnode before making a new one. |
1810 | | - * @note Every call to this function, even if the same parameter id is used, returns |
1811 | | - * a new bnode id! |
| 1701 | + * Get a numeric ID for some Bnode ("internal object") that is to be used |
| 1702 | + * to encode a container property value. Bnodes are managed through the |
| 1703 | + * smw_ids table but will always have an empty smw_title, and smw_namespace |
| 1704 | + * being set to the parent object (the id of the page that uses the Bnode). |
| 1705 | + * Unused Bnodes are not deleted but marked as available by setting |
| 1706 | + * smw_namespace to 0. This method then tries to reuse an unused bnode |
| 1707 | + * before making a new one. |
| 1708 | + * @note Every call to this function, even if the same parameter id is |
| 1709 | + * used, returns a new bnode id! |
1812 | 1710 | */ |
1813 | 1711 | protected function makeSMWBnodeID($sid) { |
1814 | 1712 | $db =& wfGetDB( DB_MASTER ); |
1815 | | - $id = 0; |
1816 | 1713 | // check if there is an unused bnode to take: |
1817 | | - $res = $db->select( |
1818 | | - 'smw_ids', |
1819 | | - 'smw_id', |
1820 | | - 'smw_title=' . $db->addQuotes('') . ' AND ' . 'smw_namespace=' . $db->addQuotes(0) . ' AND smw_iw=' . $db->addQuotes(SMW_SQL2_SMWIW), |
1821 | | - 'SMW::makeSMWBnodeID', |
1822 | | - array( 'LIMIT' => 1 ) |
1823 | | - ); |
1824 | | - if ($row = $db->fetchObject($res)) { |
1825 | | - $id = $row->smw_id; |
1826 | | - } |
| 1714 | + $res = $db->select( 'smw_ids', 'smw_id', array('smw_title' => '', 'smw_namespace' => 0, 'smw_iw' => SMW_SQL2_SMWIW), |
| 1715 | + 'SMW::makeSMWBnodeID', array( 'LIMIT' => 1 ) ); |
| 1716 | + $id = ($row = $db->fetchObject($res))?$row->smw_id:0; |
| 1717 | + $db->freeResult($res); |
1827 | 1718 | // claim that bnode: |
1828 | 1719 | if ($id != 0) { |
1829 | | - $db->update( |
1830 | | - 'smw_ids', |
1831 | | - array( 'smw_namespace' => $sid ), |
1832 | | - array( |
1833 | | - 'smw_id'=>$id, |
1834 | | - 'smw_title' => '', |
1835 | | - 'smw_namespace' => 0, |
1836 | | - 'smw_iw' => SMW_SQL2_SMWIW |
1837 | | - ), |
1838 | | - 'SMW::makeSMWBnodeID', |
1839 | | - array( 'LIMIT'=>1 ) |
1840 | | - ); |
| 1720 | + $db->update( 'smw_ids', array( 'smw_namespace' => $sid ), |
| 1721 | + array( 'smw_id'=>$id, |
| 1722 | + 'smw_title' => '', |
| 1723 | + 'smw_namespace' => 0, |
| 1724 | + 'smw_iw' => SMW_SQL2_SMWIW), 'SMW::makeSMWBnodeID', array( 'LIMIT'=>1 ) ); |
1841 | 1725 | if ($db->affectedRows() == 0) { // Oops, someone was faster (collisions are possible here, no locks) |
1842 | 1726 | $id = 0; // fallback: make a new node (TODO: we could also repeat to try another ID) |
1843 | 1727 | } |
1844 | 1728 | } |
1845 | 1729 | // if no node was found yet, make a new one: |
1846 | 1730 | if ($id == 0) { |
1847 | | - $db->insert('smw_ids', array( |
1848 | | - 'smw_id' => $db->nextSequenceValue('smw_ids_smw_id_seq'), |
1849 | | - 'smw_title' => '', |
1850 | | - 'smw_namespace' => $sid, |
1851 | | - 'smw_iw' => SMW_SQL2_SMWIW), 'SMW::makeSMWBnodeID' |
1852 | | - ); |
| 1731 | + $db->insert( 'smw_ids', |
| 1732 | + array( 'smw_id' => $db->nextSequenceValue('smw_ids_smw_id_seq'), |
| 1733 | + 'smw_title' => '', |
| 1734 | + 'smw_namespace' => $sid, |
| 1735 | + 'smw_iw' => SMW_SQL2_SMWIW), 'SMW::makeSMWBnodeID' ); |
1853 | 1736 | $id = $db->insertId(); |
1854 | 1737 | } |
1855 | 1738 | return $id; |
1856 | 1739 | } |
1857 | 1740 | |
1858 | 1741 | /** |
1859 | | - * Change an internal id to another value. If no target value is given, the value is changed |
1860 | | - * to become the last id entry (based on the automatic id increment of the database). Whatever |
1861 | | - * currently occupies this id will be moved consistently in all relevant tables. Whatever |
1862 | | - * currently occupies the target id will be ignored (it should be ensured that nothing is moved |
1863 | | - * to an id that is still in use somewhere). |
| 1742 | + * Change an internal id to another value. If no target value is given, the |
| 1743 | + * value is changed to become the last id entry (based on the automatic id |
| 1744 | + * increment of the database). Whatever currently occupies this id will be |
| 1745 | + * moved consistently in all relevant tables. Whatever currently occupies |
| 1746 | + * the target id will be ignored (it should be ensured that nothing is |
| 1747 | + * moved to an id that is still in use somewhere). |
| 1748 | + * @note This page does not update any caches. If relevant, this needs to |
| 1749 | + * be effected by the caller. |
1864 | 1750 | */ |
1865 | | - protected function moveID($curid, $targetid = 0) { |
| 1751 | + protected function moveSMWPageID($curid, $targetid = 0) { |
1866 | 1752 | $db =& wfGetDB( DB_MASTER ); |
1867 | | - $row = $db->selectRow( |
1868 | | - 'smw_ids', |
1869 | | - array( 'smw_id', 'smw_namespace', 'smw_title', 'smw_iw', 'smw_sortkey' ), |
1870 | | - array( 'smw_id' => $curid ), |
1871 | | - 'SMWSQLStore2::moveID' |
1872 | | - ); |
| 1753 | + $row = $db->selectRow( 'smw_ids', |
| 1754 | + array( 'smw_id', 'smw_namespace', 'smw_title', 'smw_iw', 'smw_sortkey' ), |
| 1755 | + array( 'smw_id' => $curid ), 'SMWSQLStore2::moveSMWPageID' ); |
1873 | 1756 | if ($row === false) return; // no id at current position, ignore |
1874 | | - if ($targetid == 0) { |
1875 | | - $db->insert('smw_ids', |
1876 | | - array( |
1877 | | - 'smw_id' => $db->nextSequenceValue('smw_ids_smw_id_seq'), |
1878 | | - 'smw_title' => $row->smw_title, |
1879 | | - 'smw_namespace' => $row->smw_namespace, |
1880 | | - 'smw_iw' => $row->smw_iw, |
1881 | | - 'smw_sortkey' => $row->smw_sortkey |
1882 | | - ), |
1883 | | - 'SMW::moveID' |
1884 | | - ); |
| 1757 | + if ($targetid == 0) { // append new id |
| 1758 | + $db->insert('smw_ids', array( 'smw_id' => $db->nextSequenceValue('smw_ids_smw_id_seq'), |
| 1759 | + 'smw_title' => $row->smw_title, |
| 1760 | + 'smw_namespace' => $row->smw_namespace, |
| 1761 | + 'smw_iw' => $row->smw_iw, |
| 1762 | + 'smw_sortkey' => $row->smw_sortkey ), 'SMW::moveSMWPageID' ); |
1885 | 1763 | $targetid = $db->insertId(); |
1886 | | - } else { |
1887 | | - $db->insert('smw_ids', |
1888 | | - array( |
1889 | | - 'smw_id' => $targetid, |
1890 | | - 'smw_title' => $row->smw_title, |
1891 | | - 'smw_namespace' => $row->smw_namespace, |
1892 | | - 'smw_iw' => $row->smw_iw, |
1893 | | - 'smw_sortkey' => $row->smw_sortkey |
1894 | | - ), |
1895 | | - 'SMW::moveID' |
1896 | | - ); |
| 1764 | + } else { // change to given id |
| 1765 | + $db->insert('smw_ids', array( 'smw_id' => $targetid, |
| 1766 | + 'smw_title' => $row->smw_title, |
| 1767 | + 'smw_namespace' => $row->smw_namespace, |
| 1768 | + 'smw_iw' => $row->smw_iw, |
| 1769 | + 'smw_sortkey' => $row->smw_sortkey ), 'SMW::moveSMWPageID' ); |
1897 | 1770 | } |
1898 | | - $db->delete('smw_ids', array('smw_id'=>$curid), 'SMWSQLStore2::moveID'); |
1899 | | - // Bnode references use namespace field to store ids: |
1900 | | - $db->update('smw_ids', |
1901 | | - array('smw_namespace' => $targetid), |
1902 | | - array('smw_title' => '', 'smw_namespace' => $curid, 'smw_iw' => SMW_SQL2_SMWIW), |
1903 | | - 'SMW::moveID' |
1904 | | - ); |
| 1771 | + $db->delete('smw_ids', array('smw_id'=>$curid), 'SMWSQLStore2::moveSMWPageID'); |
| 1772 | + $this->changeSMWPageID($curid,$targetid,$row->smw_namespace,$row->smw_namespace); |
| 1773 | + } |
1905 | 1774 | |
1906 | | - // now change all id entries in all other tables: |
1907 | | - $cond_array = array( 's_id' => $curid ); |
1908 | | - $val_array = array( 's_id' => $targetid ); |
1909 | | - $db->update('smw_rels2', $val_array, $cond_array, 'SMW::moveID'); |
1910 | | - $db->update('smw_atts2', $val_array, $cond_array, 'SMW::moveID'); |
1911 | | - $db->update('smw_text2', $val_array, $cond_array, 'SMW::moveID'); |
1912 | | - $db->update('smw_spec2', $val_array, $cond_array, 'SMW::moveID'); |
1913 | | - $db->update('smw_subs2', $val_array, $cond_array, 'SMW::moveID'); |
1914 | | - $db->update('smw_subp2', $val_array, $cond_array, 'SMW::moveID'); |
1915 | | - $db->update('smw_inst2', $val_array, $cond_array, 'SMW::moveID'); |
1916 | | - if ($row->smw_namespace == SMW_NS_CONCEPT) { |
1917 | | - $db->update('smw_conc2', $val_array, $cond_array, 'SMW::moveID'); |
| 1775 | + /** |
| 1776 | + * Change an SMW page id across all relevant tables. The id in smw_ids as |
| 1777 | + * such is not touched, but bnodes refering to the old object will be moved |
| 1778 | + * along. The redirect table is also updated (without much effect if the |
| 1779 | + * change happended due to some redirect, since the table should not |
| 1780 | + * contain the id of the redirected page). If namespaces are given, then |
| 1781 | + * they are used to delete any entries that are limited to one particular |
| 1782 | + * namespace (e.g. only properties can be used as properties) instead of |
| 1783 | + * moving them. |
| 1784 | + * |
| 1785 | + * @param $oldid numeric ID that is to be changed |
| 1786 | + * @param $newid numeric ID to which the records are to be changed |
| 1787 | + * @param $oldnamespace namespace of old id's page (-1 to ignore it) |
| 1788 | + * @param $newnamespace namespace of new id's page (-1 to ignore it) |
| 1789 | + * @param $sdata boolean stating whether to update subject references |
| 1790 | + * @param $podata boolean stating if to update property/object references |
| 1791 | + */ |
| 1792 | + protected function changeSMWPageID($oldid,$newid,$oldnamespace=-1,$newnamespace=-1, $sdata=true, $podata=true) { |
| 1793 | + $fname = 'SMW::changeSMWPageID'; |
| 1794 | + // Update bnode references that use namespace field to store ids: |
| 1795 | + if ($sdata) { // bnodes are part of the data of a subject |
| 1796 | + $db->update('smw_ids', array('smw_namespace' => $newid), |
| 1797 | + array('smw_title' => '', 'smw_namespace' => $oldid, 'smw_iw' => SMW_SQL2_SMWIW), $fname ); |
1918 | 1798 | } |
1919 | | - $db->update('smw_conccache', $val_array, $cond_array, 'SMW::moveID'); |
1920 | | - if ($row->smw_namespace == SMW_NS_PROPERTY) { |
1921 | | - $cond_array = array( 'p_id' => $curid ); |
1922 | | - $val_array = array( 'p_id' => $targetid ); |
1923 | | - $db->update('smw_rels2', $val_array, $cond_array, 'SMW::moveID'); |
1924 | | - $db->update('smw_atts2', $val_array, $cond_array, 'SMW::moveID'); |
1925 | | - $db->update('smw_text2', $val_array, $cond_array, 'SMW::moveID'); |
1926 | | - $db->update('smw_spec2', $val_array, $cond_array, 'SMW::moveID'); |
| 1799 | + // change all id entries in property tables: |
| 1800 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 1801 | + if ($sdata && $proptable->idsubject) { |
| 1802 | + $db->update($proptable->name, array('s_id'=>$newid), array('s_id'=>$oldid), $fname); |
| 1803 | + } |
| 1804 | + if ($podata) { |
| 1805 | + if ( (($oldnamespace == -1) || ($oldnamespace == SMW_NS_PROPERTY)) && ($proptable->fixedproperty == false) ) { |
| 1806 | + if (($newnamespace == -1) || ($newnamespace == SMW_NS_PROPERTY)) { |
| 1807 | + $db->update($proptable->name, array('p_id'=>$newid), array('p_id'=>$oldid), $fname); |
| 1808 | + } else { |
| 1809 | + $db->delete($proptable->name, array('p_id'=>$oldid), $fname); |
| 1810 | + } |
| 1811 | + } |
| 1812 | + foreach ($proptable->objectfields as $fieldname => $type) { |
| 1813 | + if ($type == 'p') { |
| 1814 | + $db->update($proptable->name, array($fieldname=>$newid), array($fieldname=>$oldid), $fname); |
| 1815 | + } |
| 1816 | + } |
| 1817 | + } |
1927 | 1818 | } |
1928 | | - $cond_array = array( 'o_id' => $curid ); |
1929 | | - $val_array = array( 'o_id' => $targetid ); |
1930 | | - $db->update('smw_redi2', $val_array, $cond_array, 'SMW::moveID'); |
1931 | | - $db->update('smw_rels2', $val_array, $cond_array, 'SMW::moveID'); |
1932 | | - $db->update('smw_subs2', $val_array, $cond_array, 'SMW::moveID'); |
1933 | | - $db->update('smw_subp2', $val_array, $cond_array, 'SMW::moveID'); |
1934 | | - $db->update('smw_inst2', $val_array, $cond_array, 'SMW::moveID'); |
1935 | | - $db->update('smw_conccache', $val_array, $cond_array, 'SMW::moveID'); |
| 1819 | + // change id entries in concept-related tables: |
| 1820 | + if ( $sdata && ( ($oldnamespace == -1) || ($oldnamespace == SMW_NS_CONCEPT) ) ) { |
| 1821 | + if ( ($newnamespace == -1) || ($newnamespace == SMW_NS_CONCEPT) ) { |
| 1822 | + $db->update('smw_conc2', array( 's_id' => $newid ), array( 's_id' => $oldid ), $fname); |
| 1823 | + $db->update('smw_conccache', array( 's_id' => $newid ), array( 's_id' => $oldid ), $fname); |
| 1824 | + } else { |
| 1825 | + $db->delete('smw_conc2', array( 's_id' => $oldid ), $fname); |
| 1826 | + $db->delete('smw_conccache', array( 's_id' => $oldid ), $fname); |
| 1827 | + } |
| 1828 | + } |
| 1829 | + if ($podata) { |
| 1830 | + $db->update('smw_conccache', array( 'o_id' => $newid ), array( 'o_id' => $oldid ), $fname); |
| 1831 | + } |
1936 | 1832 | } |
1937 | 1833 | |
1938 | 1834 | /** |
1939 | | - * Delete all semantic data stored for the given subject. |
1940 | | - * Used for update purposes. |
| 1835 | + * Delete all semantic data stored for the given subject. Used for update |
| 1836 | + * purposes. |
1941 | 1837 | */ |
1942 | | - public function deleteSemanticData(SMWWikiPageValue $subject) { |
| 1838 | + protected function deleteSemanticData(SMWWikiPageValue $subject) { |
1943 | 1839 | $db =& wfGetDB( DB_MASTER ); |
1944 | | - /// NOTE: redirects are handled by updateRedirects(), not here! |
1945 | | - //$db->delete('smw_redi2', array('s_title' => $subject->getDBkey(),'s_namespace' => $subject->getNamespace()), 'SMW::deleteSubject::Redi2'); |
| 1840 | + $fname = 'SMW::deleteSemanticData'; |
1946 | 1841 | $id = $this->getSMWPageID($subject->getDBkey(),$subject->getNamespace(),$subject->getInterwiki(),false); |
1947 | 1842 | if ($id == 0) return; // not (directly) used anywhere yet, maybe a redirect but we do not care here |
1948 | | - $db->delete('smw_rels2', array('s_id' => $id), 'SMW::deleteSubject::Rels2'); |
1949 | | - $db->delete('smw_atts2', array('s_id' => $id), 'SMW::deleteSubject::Atts2'); |
1950 | | - $db->delete('smw_text2', array('s_id' => $id), 'SMW::deleteSubject::Text2'); |
1951 | | - $db->delete('smw_spec2', array('s_id' => $id), 'SMW::deleteSubject::Spec2'); |
1952 | | - $db->delete('smw_inst2', array('s_id' => $id), 'SMW::deleteSubject::Inst2'); |
1953 | | - if ($subject->getNamespace() == NS_CATEGORY) { |
1954 | | - $db->delete('smw_subs2', array('s_id' => $id), 'SMW::deleteSubject::Subs2'); |
1955 | | - } elseif ($subject->getNamespace() == SMW_NS_PROPERTY) { |
1956 | | - $db->delete('smw_subp2', array('s_id' => $id), 'SMW::deleteSubject::Subp2'); |
| 1843 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 1844 | + if ($proptable->idsubject) { |
| 1845 | + $db->delete($proptable->name, array('s_id' => $id), $fname); |
| 1846 | + } elseif ($proptable->name != 'smw_redi') { /// NOTE: redirects are handled by updateRedirects(), not here! |
| 1847 | + $db->delete($proptable->name, array('s_title' => $subject->getDBkey(), 's_namespace' => $subject->getNamespace()), $fname); |
| 1848 | + } |
1957 | 1849 | } |
1958 | | - |
1959 | | - // find bnodes used by this ID ... |
1960 | | - $res = $db->select( |
1961 | | - 'smw_ids', |
1962 | | - 'smw_id','smw_title=' . $db->addQuotes('') . ' AND smw_namespace=' . $db->addQuotes($id) . ' AND smw_iw=' . $db->addQuotes(SMW_SQL2_SMWIW), |
1963 | | - 'SMW::deleteSubject::Nary' |
1964 | | - ); |
| 1850 | + // also find bnodes used by this ID ... |
| 1851 | + $res = $db->select( 'smw_ids', 'smw_id', array('smw_title' => '', 'smw_namespace' => $id, 'smw_iw' => SMW_SQL2_SMWIW), $fname ); |
1965 | 1852 | // ... and delete them as well |
1966 | 1853 | while ($row = $db->fetchObject($res)) { |
1967 | | - $db->delete('smw_rels2', array('s_id' => $row->smw_id), 'SMW::deleteSubject::NaryRels2'); |
1968 | | - $db->delete('smw_atts2', array('s_id' => $row->smw_id), 'SMW::deleteSubject::NaryAtts2'); |
1969 | | - $db->delete('smw_text2', array('s_id' => $row->smw_id), 'SMW::deleteSubject::NaryText2'); |
| 1854 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 1855 | + if ($proptable->idsubject) { |
| 1856 | + $db->delete($proptable->name, array('s_id' => $row->smw_id), $fname ); |
| 1857 | + } |
| 1858 | + } |
1970 | 1859 | } |
1971 | 1860 | $db->freeResult($res); |
1972 | 1861 | // free all affected bnodes in one call: |
1973 | | - $db->update( |
1974 | | - 'smw_ids', |
1975 | | - array( 'smw_namespace' => 0 ), |
1976 | | - array( 'smw_title' => '', 'smw_namespace' => $id, 'smw_iw' => SMW_SQL2_SMWIW ), |
1977 | | - 'SMW::deleteSubject::NaryIds' |
1978 | | - ); |
| 1862 | + $db->update( 'smw_ids', array( 'smw_namespace' => 0 ), array( 'smw_title' => '', 'smw_namespace' => $id, 'smw_iw' => SMW_SQL2_SMWIW ), $fname ); |
1979 | 1863 | wfRunHooks('smwDeleteSemanticData', array($subject)); |
1980 | 1864 | } |
1981 | 1865 | |
1982 | 1866 | /** |
1983 | | - * Trigger all necessary updates for redirect structure on creation, change, and deletion |
1984 | | - * of redirects. The title+namespace of the affected page and of its updated redirect |
1985 | | - * target are given. The target can be empty ('') if none is specified. |
1986 | | - * Returns the canonical ID that is now to be used for the subject, or 0 if the subject did |
1987 | | - * not occur anywhere yet. |
1988 | | - * @note This method must do a lot of updates right, and some care is needed to not confuse |
1989 | | - * ids or forget relevant tables. Please make sure you understand the relevant cases before |
1990 | | - * making changes, especially since errors may go unnoticed for some time. |
| 1867 | + * Helper method to write information about some redirect. Various updates |
| 1868 | + * can be necessary if redirects are resolved as identities SMW. The title |
| 1869 | + * and namespace of the affected page and of its updated redirect target |
| 1870 | + * are given. The target can be empty ('') to delete any redirect. Returns |
| 1871 | + * the canonical ID that is now to be used for the subject. |
| 1872 | + * |
| 1873 | + * This method does not change the ids of the affected pages, and thus it |
| 1874 | + * is not concerned with updates of the data that is currently stored for |
| 1875 | + * the subject. Normally, a subject that is a redirect will not have other |
| 1876 | + * data, but this method does not depend upon this in any way. |
| 1877 | + * |
| 1878 | + * @note Please make sure you fully understand this code before making any |
| 1879 | + * changes here. Keeping the redirect structure consistent is important, |
| 1880 | + * and errors in this code can go unnoticed for quite some time. |
1991 | 1881 | */ |
1992 | 1882 | protected function updateRedirects($subject_t, $subject_ns, $curtarget_t='', $curtarget_ns=-1) { |
1993 | 1883 | global $smwgQEqualitySupport, $smwgEnableUpdateJobs; |
| 1884 | + $fname = 'SMW::updateRedirects'; |
| 1885 | + |
| 1886 | + //*** First get id of subject, old redirect target, and current (new) redirect target ***// |
1994 | 1887 | $sid = $this->getSMWPageID($subject_t, $subject_ns, '', false); // find real id of subject, if any |
1995 | 1888 | /// NOTE: $sid can be 0 here; this is useful to know since it means that fewer table updates are needed |
| 1889 | + $new_tid = $curtarget_t?($this->makeSMWPageID($curtarget_t, $curtarget_ns, '', false)):0; // real id of new target, if given |
1996 | 1890 | $db =& wfGetDB( DB_SLAVE ); |
1997 | | - $res = $db->select( array('smw_redi2'),'o_id','s_title=' . $db->addQuotes($subject_t) . |
1998 | | - ' AND s_namespace=' . $db->addQuotes($subject_ns), |
1999 | | - 'SMW::updateRedirects', array('LIMIT' => 1) ); |
| 1891 | + $res = $db->select( array('smw_redi2'),'o_id', array('s_title' => $subject_t, 's_namespace' => $subject_ns ), $fname, array('LIMIT' => 1) ); |
2000 | 1892 | $old_tid = ($row = $db->fetchObject($res))?$row->o_id:0; // real id of old target, if any |
2001 | 1893 | $db->freeResult($res); |
2002 | | - $new_tid = $curtarget_t?($this->makeSMWPageID($curtarget_t, $curtarget_ns, '', false)):0; // real id of new target |
2003 | 1894 | /// NOTE: $old_tid and $new_tid both ignore further redirects, (intentionally) no redirect chains! |
| 1895 | + |
2004 | 1896 | if ($old_tid == $new_tid) { // no change, all happy |
2005 | 1897 | return ($new_tid==0)?$sid:$new_tid; |
2006 | | - } |
| 1898 | + } // note that this means $old_tid!=$new_tid in all cases below |
| 1899 | + |
| 1900 | + //*** Make relevant changes in property tables (don't write the new redirect yet) ***// |
2007 | 1901 | $db =& wfGetDB( DB_MASTER ); // now we need to write something |
2008 | | - if ( ($old_tid == 0) && ($sid != 0) && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { |
2009 | | - // new redirect, directly change object entries of $sid to $new_tid |
2010 | | - /// NOTE: if $sid == 0, then nothing needs to be done here |
2011 | | - $db->update('smw_rels2', array( 'o_id' => $new_tid ), array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2012 | | - if ( ( $subject_ns == SMW_NS_PROPERTY ) && ( $curtarget_ns == SMW_NS_PROPERTY ) ) { |
2013 | | - $cond_array = array( 'p_id' => $sid ); |
2014 | | - $val_array = array( 'p_id' => $new_tid ); |
2015 | | - $db->update('smw_rels2', $val_array, $cond_array, 'SMW::updateRedirects'); |
2016 | | - $db->update('smw_atts2', $val_array, $cond_array, 'SMW::updateRedirects'); |
2017 | | - $db->update('smw_text2', $val_array, $cond_array, 'SMW::updateRedirects'); |
2018 | | - $db->update('smw_subp2', array( 'o_id' => $new_tid ), array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2019 | | - } elseif ($subject_ns == SMW_NS_PROPERTY) { // delete triples that are only allowed for properties |
2020 | | - $db->delete('smw_rels2', array( 'p_id' => $sid ), 'SMW::updateRedirects'); |
2021 | | - $db->delete('smw_atts2', array( 'p_id' => $sid ), 'SMW::updateRedirects'); |
2022 | | - $db->delete('smw_text2', array( 'p_id' => $sid ), 'SMW::updateRedirects'); |
2023 | | - $db->delete('smw_subp2', array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2024 | | - } elseif ( ( $subject_ns == NS_CATEGORY ) && ( $curtarget_ns == NS_CATEGORY ) ) { |
2025 | | - $db->update('smw_subs2', array( 'o_id' => $new_tid ), array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2026 | | - $db->update('smw_inst2', array( 'o_id' => $new_tid ), array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2027 | | - } elseif ($subject_ns == NS_CATEGORY) { // delete triples that are only allowed for categories |
2028 | | - $db->delete('smw_subs2', array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2029 | | - $db->delete('smw_inst2', array( 'o_id' => $sid ), 'SMW::updateRedirects'); |
2030 | | - } |
2031 | | - } elseif ($old_tid != 0) { // existing redirect is overwritten |
2032 | | - // we do not know which entries of $old_tid are now $new_tid/$sid |
2033 | | - // -> ask SMW to update all affected pages as soon as possible (using jobs) |
2034 | | - //first delete the existing redirect: |
2035 | | - $db->delete('smw_redi2', array('s_title' => $subject_t,'s_namespace' => $subject_ns), 'SMW::updateRedirects'); |
2036 | | - if ( $smwgEnableUpdateJobs && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { // further updates if equality reasoning is enabled |
| 1902 | + if ( ($old_tid == 0) && ($sid != 0) && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { // new redirect |
| 1903 | + // $smwgQEqualitySupport requires us to change all tables' page references from $sid to $new_tid. |
| 1904 | + // Since references must not be 0, we don't have to do this is $sid == 0. |
| 1905 | + $this->changeSMWPageID($sid, $new_tid, $subject_ns, $curtarget_ns, false, true); |
| 1906 | + } elseif ($old_tid != 0) { // existing redirect is changed or deleted |
| 1907 | + $db->delete('smw_redi2', array('s_title' => $subject_t,'s_namespace' => $subject_ns), $fname); |
| 1908 | + |
| 1909 | + if ( $smwgEnableUpdateJobs && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { |
| 1910 | + // entries that refer to old target may in fact refer to subject, but we don't know which: schedule affected pages for update |
2037 | 1911 | $jobs = array(); |
2038 | | - $res = $db->select( array('smw_rels2','smw_ids'),'DISTINCT smw_title,smw_namespace', |
2039 | | - 's_id=smw_id AND o_id=' . $db->addQuotes($old_tid), |
2040 | | - 'SMW::updateRedirects'); |
2041 | | - while ($row = $db->fetchObject($res)) { |
2042 | | - $t = Title::makeTitle($row->smw_namespace,$row->smw_title); |
2043 | | - $jobs[] = new SMWUpdateJob($t); |
2044 | | - } |
2045 | | - $db->freeResult($res); |
2046 | | - if ( $subject_ns == SMW_NS_PROPERTY ) { |
2047 | | - /// TODO: this would be more efficient if we would know the type of the |
2048 | | - /// property, but the current architecture deletes this first (PERFORMANCE) |
2049 | | - foreach (array('smw_rels2','smw_atts2','smw_text2') as $table) { |
2050 | | - $res = $db->select( array($table,'smw_ids'),'DISTINCT smw_title,smw_namespace', |
2051 | | - 's_id=smw_id AND p_id=' . $db->addQuotes($old_tid), |
2052 | | - 'SMW::updateRedirects'); |
| 1912 | + foreach (SMWSQLStore2::getPropertyTables() as $proptable) { |
| 1913 | + if ($proptable->name == 'smw_redi2') continue; // can safely be skipped |
| 1914 | + if ( $proptable->idsubject ) { |
| 1915 | + $from = $db->tableName($proptable->name) . ' INNER JOIN ' . $db->tableName('smw_ids') . ' ON s_id=smw_id'; |
| 1916 | + $select = 'DISTINCT smw_title AS title,smw_namespace AS namespace'; |
| 1917 | + } else { |
| 1918 | + $from = $db->tableName($proptable->name); |
| 1919 | + $select = 'DISTINCT s_title AS title,s_namespace AS namespace'; |
| 1920 | + } |
| 1921 | + if ( ($subject_ns == SMW_NS_PROPERTY) && ($proptable->fixedproperty == false) ) { |
| 1922 | + $res = $db->select( $from, $select, array('p_id' => $old_tid), $fname); |
2053 | 1923 | while ($row = $db->fetchObject($res)) { |
2054 | | - $t = Title::makeTitle($row->smw_namespace,$row->smw_title); |
2055 | | - $jobs[] = new SMWUpdateJob($t); |
| 1924 | + $jobs[] = new SMWUpdateJob(Title::makeTitle($row->namespace,$row->title)); |
2056 | 1925 | } |
| 1926 | + $db->freeResult($res); |
2057 | 1927 | } |
2058 | | - $res = $db->select( array('smw_subp2','smw_ids'),'DISTINCT smw_title,smw_namespace', |
2059 | | - 's_id=smw_id AND o_id=' . $db->addQuotes($old_tid), |
2060 | | - 'SMW::updateRedirects'); |
2061 | | - while ($row = $db->fetchObject($res)) { |
2062 | | - $t = Title::makeTitle($row->smw_namespace,$row->smw_title); |
2063 | | - $jobs[] = new SMWUpdateJob($t); |
2064 | | - } |
2065 | | - } elseif ( $subject_ns == NS_CATEGORY ) { |
2066 | | - foreach (array('smw_subs2','smw_inst2') as $table) { |
2067 | | - $res = $db->select( array($table,'smw_ids'),'DISTINCT smw_title,smw_namespace', |
2068 | | - 's_id=smw_id AND o_id=' . $db->addQuotes($old_tid), |
2069 | | - 'SMW::updateRedirects'); |
2070 | | - while ($row = $db->fetchObject($res)) { |
2071 | | - $t = Title::makeTitle($row->smw_namespace,$row->smw_title); |
2072 | | - $jobs[] = new SMWUpdateJob($t); |
| 1928 | + foreach ($proptable->objectfields as $fieldname => $type) { |
| 1929 | + if ($type == 'p') { |
| 1930 | + $res = $db->select( $from, $select, array($fieldname => $old_tid), $fname); |
| 1931 | + while ($row = $db->fetchObject($res)) { |
| 1932 | + $jobs[] = new SMWUpdateJob(Title::makeTitle($row->namespace,$row->title)); |
| 1933 | + } |
| 1934 | + $db->freeResult($res); |
2073 | 1935 | } |
2074 | 1936 | } |
2075 | 1937 | } |
| 1938 | + /// NOTE: we do not update the concept cache here; this remains an offline task |
2076 | 1939 | Job::batchInsert($jobs); ///NOTE: this only happens if $smwgEnableUpdateJobs was true above |
2077 | 1940 | } |
2078 | 1941 | } |
2079 | | - // finally, write the new redirect AND refresh your internal canonical id cache! |
2080 | | - if ($sid == 0) { |
2081 | | - $sid = $this->makeSMWPageID($subject_t, $subject_ns, '', false); |
2082 | | - } |
2083 | | - if ($new_tid != 0) { |
2084 | | - $db->insert( 'smw_redi2', array('s_title'=>$subject_t, 's_namespace'=>$subject_ns, 'o_id'=>$new_tid), 'SMW::updateRedirects'); |
2085 | | - if ($smwgQEqualitySupport != SMW_EQ_NONE) { |
2086 | | - $db->update('smw_ids', array('smw_iw'=>SMW_SQL2_SMWREDIIW), array('smw_id'=>$sid), 'SMW::updateRedirects'); |
| 1942 | + |
| 1943 | + //*** Finally, write the new redirect data ***// |
| 1944 | + if ($new_tid != 0) { // record new redirect |
| 1945 | + // Redirecting done right: |
| 1946 | + // make a new ID with iw SMW_SQL2_SMWREDIIW or change iw field of current ID in this way, write smw_redi2 table, update canonical cache |
| 1947 | + // This order must be obeyed unless you really understand what you are doing! |
| 1948 | + if ( ($old_tid == 0) && ($smwgQEqualitySupport != SMW_EQ_NONE) ) { // mark subject as redirect (if it was no redirect before) |
| 1949 | + if ($sid == 0) { // every redirect page must have an ID |
| 1950 | + $sid = $this->makeSMWPageID($subject_t, $subject_ns, SMW_SQL2_SMWREDIIW, false); |
| 1951 | + } else { |
| 1952 | + $db->update('smw_ids', array('smw_iw'=>SMW_SQL2_SMWREDIIW), array('smw_id'=>$sid), $fname); |
| 1953 | + } |
2087 | 1954 | } |
| 1955 | + $db->insert( 'smw_redi2', array('s_title'=>$subject_t, 's_namespace'=>$subject_ns, 'o_id'=>$new_tid), $fname); |
2088 | 1956 | $this->m_ids[" $subject_ns $subject_t C"] = $new_tid; // "iw" is empty here |
2089 | | - } else { |
| 1957 | + } else { // delete old redirect |
| 1958 | + // This case implies $old_tid != 0 (or we would have new_tid == old_tid above). |
| 1959 | + // Therefore $subject had a redirect, and it must also have an ID. This shows that $sid != 0 here. |
2090 | 1960 | $this->m_ids[" $subject_ns $subject_t C"] = $sid; // "iw" is empty here |
2091 | | - if ($smwgQEqualitySupport != SMW_EQ_NONE) { |
2092 | | - $db->update('smw_ids', array('smw_iw'=>''), array('smw_id'=>$sid), 'SMW::updateRedirects'); |
| 1961 | + if ($smwgQEqualitySupport != SMW_EQ_NONE) { // mark subject as non-redirect |
| 1962 | + $db->update('smw_ids', array('smw_iw'=>''), array('smw_id'=>$sid), $fname); |
2093 | 1963 | } |
2094 | 1964 | } |
2095 | | - // just flush those caches to be safe, they are not essential in program runs with redirect updates |
| 1965 | + //*** Flush some caches to be safe, though they are not essential in program runs with redirect updates ***// |
2096 | 1966 | unset($this->m_semdata[$sid]); unset($this->m_semdata[$new_tid]); unset($this->m_semdata[$old_tid]); |
2097 | 1967 | unset($this->m_sdstate[$sid]); unset($this->m_sdstate[$new_tid]); unset($this->m_sdstate[$old_tid]); |
2098 | 1968 | return ($new_tid==0)?$sid:$new_tid; |
2099 | 1969 | } |
2100 | 1970 | |
| 1971 | + /// @todo Document. |
| 1972 | + public static function getPropertyTables() { |
| 1973 | + if (count(SMWSQLStore2::$prop_tables) > 0) return SMWSQLStore2::$prop_tables; // don't initialise twice |
| 1974 | + // type constants: t (title string), u (unit string), l (long string), 'f' (float), 'i' (integer), 'j' (unsigned integer), 'p' (page id) |
| 1975 | + SMWSQLStore2::$prop_tables['smw_rels2'] = new SMWSQLStore2Table('smw_rels2', |
| 1976 | + array('o_id' => 'p'), |
| 1977 | + array('o_id')); |
| 1978 | + SMWSQLStore2::$prop_tables['smw_atts2'] = new SMWSQLStore2Table('smw_atts2', |
| 1979 | + array('value_xsd' => 't', 'value_num' => 'f', 'value_unit' => 'u' ), |
| 1980 | + array('value_num','value_xsd')); |
| 1981 | + SMWSQLStore2::$prop_tables['smw_text2'] = new SMWSQLStore2Table('smw_text2', |
| 1982 | + array('value_blob' => 'l')); |
| 1983 | + SMWSQLStore2::$prop_tables['smw_spec2'] = new SMWSQLStore2Table('smw_spec2', |
| 1984 | + array('value_string' => 't' ), |
| 1985 | + array('s_id,p_id')); |
| 1986 | + SMWSQLStore2::$prop_tables['smw_spec2']->specpropsonly = true; |
| 1987 | + SMWSQLStore2::$prop_tables['smw_subs2'] = new SMWSQLStore2Table('smw_subs2', |
| 1988 | + array('o_id' => 'p' ), |
| 1989 | + array('o_id'), |
| 1990 | + '_SUBC'); |
| 1991 | + SMWSQLStore2::$prop_tables['smw_subp2'] = new SMWSQLStore2Table('smw_subp2', |
| 1992 | + array('o_id' => 'p' ), |
| 1993 | + array('o_id'), |
| 1994 | + '_SUBP'); |
| 1995 | + SMWSQLStore2::$prop_tables['smw_inst2'] = new SMWSQLStore2Table('smw_inst2', |
| 1996 | + array('o_id' => 'p' ), |
| 1997 | + array('o_id'), |
| 1998 | + '_INST'); |
| 1999 | + SMWSQLStore2::$prop_tables['smw_redi2'] = new SMWSQLStore2Table('smw_redi2', |
| 2000 | + array('o_id' => 'p' ), |
| 2001 | + array('o_id'), |
| 2002 | + '_REDI'); |
| 2003 | + SMWSQLStore2::$prop_tables['smw_redi2']->idsubject = false; |
| 2004 | + |
| 2005 | + return SMWSQLStore2::$prop_tables; |
| 2006 | + } |
| 2007 | + |
2101 | 2008 | } |
Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2_Queries.php |
— | — | @@ -1,6 +1,6 @@ |
2 | 2 | <?php |
3 | 3 | /** |
4 | | - * Query answering functions for SMWSQLStore2. Separated frmo main code for readability and |
| 4 | + * Query answering functions for SMWSQLStore2. Separated from main code for readability and |
5 | 5 | * for avoiding twice the amount of code being required on every use of a simple storage function. |
6 | 6 | * |
7 | 7 | * @author Markus Krötzsch |
— | — | @@ -47,21 +47,21 @@ |
48 | 48 | |
49 | 49 | /// Database slave to be used |
50 | 50 | protected $m_dbs; /// TODO: should temporary tables be created on the master DB? |
51 | | - /// Parent SMWSQLStore2 |
| 51 | + /// Parent SMWSQLStore2. |
52 | 52 | protected $m_store; |
53 | | - /// Query mode copied from given query, some submethods act differently when in SMWQuery::MODE_DEBUG |
| 53 | + /// Query mode copied from given query. Some submethods act differently when in SMWQuery::MODE_DEBUG. |
54 | 54 | protected $m_qmode; |
55 | | - /// Array of generated query descriptions |
| 55 | + /// Array of generated SMWSQLStore2Query query descriptions (index => object). |
56 | 56 | protected $m_queries = array(); |
57 | | - /// Array of arrays of executed queries, indexed by the temporary table names results were fed into |
| 57 | + /// Array of arrays of executed queries, indexed by the temporary table names results were fed into. |
58 | 58 | protected $m_querylog = array(); |
59 | 59 | /** |
60 | | - * Array of sorting requests ("Property_name" => "ASC"/"DESC". Used during query |
| 60 | + * Array of sorting requests ("Property_name" => "ASC"/"DESC"). Used during query |
61 | 61 | * processing (where these property names are searched while compiling the query |
62 | 62 | * conditions). |
63 | 63 | */ |
64 | 64 | protected $m_sortkeys; |
65 | | - /// Cache of computed hierarchy queries for reuse, cat/prop-value string => tablename |
| 65 | + /// Cache of computed hierarchy queries for reuse ("catetgory/property value string" => "tablename"). |
66 | 66 | protected $m_hierarchies = array(); |
67 | 67 | /// Local collection of error strings, passed on to callers if possible. |
68 | 68 | protected $m_errors = array(); |
— | — | @@ -141,17 +141,38 @@ |
142 | 142 | } |
143 | 143 | |
144 | 144 | /** |
145 | | - * The new SQL store's implementation of query answering. |
| 145 | + * The new SQL store's implementation of query answering. This function |
| 146 | + * works in two stages: First, the nested conditions of the given query |
| 147 | + * object are preprocessed to compute an abstract representation of the |
| 148 | + * SQL query that is to be executed. Since query conditions correspond to |
| 149 | + * joins with property tables in most cases, this abstract representation |
| 150 | + * is essentially graph-like description of how property tables are joined. |
| 151 | + * Moreover, this graph is tree-shaped, since all query conditions are |
| 152 | + * tree-shaped. Each part of this abstract query structure is represented |
| 153 | + * by an SMWSQLStore2Query object in the array m_queries. |
| 154 | + * |
| 155 | + * As a second stage of processing, the thus prepared SQL query is actually |
| 156 | + * executed. Typically, this means that the joins are collapsed into one |
| 157 | + * SQL query to retrieve results. In some cases, such as in dbug mode, the |
| 158 | + * execution might be restricted and not actually perform the whole query. |
| 159 | + * |
| 160 | + * The two-stage process helps to separate tasks, and it also allows for |
| 161 | + * better optimisations: it is left to the execution engine how exactly the |
| 162 | + * query result is to be obtained. For example, one could pre-compute |
| 163 | + * partial suib-results in temporary tables (or even cache them somewhere), |
| 164 | + * instead of passing one large join query to the DB (of course, it might |
| 165 | + * be large only if the configuration of SMW allows it). For some DBMS, a |
| 166 | + * step-wise execution of the query might lead to better performance, since |
| 167 | + * it exploits the tree-structure of the joins, which is important for fast |
| 168 | + * processing -- not all DBMS might be able in seeing this by themselves. |
146 | 169 | */ |
147 | 170 | public function getQueryResult(SMWQuery $query) { |
148 | 171 | global $smwgIgnoreQueryErrors; |
149 | 172 | if (!$smwgIgnoreQueryErrors && ($query->querymode != SMWQuery::MODE_DEBUG) && (count($query->getErrors()) > 0)) { |
150 | | - $result = new SMWQueryResult($query->getDescription()->getPrintrequests(), $query, array(), $this->m_store, false); |
151 | | - return $result; |
| 173 | + return new SMWQueryResult($query->getDescription()->getPrintrequests(), $query, array(), $this->m_store, false); |
152 | 174 | /// NOTE: We currently check this only once since the below steps do not create further errors |
153 | 175 | } elseif ($query->querymode == SMWQuery::MODE_NONE) { // don't query, but return something to printer |
154 | | - $result = new SMWQueryResult($query->getDescription()->getPrintrequests(), $query, array(), $this->m_store, true); |
155 | | - return $result; |
| 176 | + return new SMWQueryResult($query->getDescription()->getPrintrequests(), $query, array(), $this->m_store, true); |
156 | 177 | } |
157 | 178 | $this->m_qmode = $query->querymode; |
158 | 179 | $this->m_queries = array(); |
— | — | @@ -161,7 +182,8 @@ |
162 | 183 | $this->m_distance = $query->getDistance(); |
163 | 184 | SMWSQLStore2Query::$qnum = 0; |
164 | 185 | $this->m_sortkeys = $query->sortkeys; |
165 | | - // build query dependency tree: |
| 186 | + |
| 187 | + //*** First compute abstract representation of the query (compilation) ***// |
166 | 188 | wfProfileIn('SMWSQLStore2Queries::compileMainQuery (SMW)'); |
167 | 189 | $qid = $this->compileQueries($query->getDescription()); // compile query, build query "plan" |
168 | 190 | wfProfileOut('SMWSQLStore2Queries::compileMainQuery (SMW)'); |
— | — | @@ -173,21 +195,22 @@ |
174 | 196 | $q->where = "$q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWIW) . " AND $q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWREDIIW) . " AND $q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWBORDERIW) . " AND $q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWINTDEFIW); |
175 | 197 | $this->m_queries[$qid] = $q; |
176 | 198 | } |
177 | | - // append query to root: |
178 | 199 | if ($this->m_queries[$qid]->jointable != 'smw_ids') { |
179 | | - // manually make final root query (to retrieve namespace,title): |
| 200 | + // manually make final root query (to retrieve namespace,title): |
180 | 201 | $rootid = SMWSQLStore2Query::$qnum; |
181 | 202 | $qobj = new SMWSQLStore2Query(); |
182 | | - $qobj->jointable = 'smw_ids'; |
183 | | - $qobj->joinfield = "$qobj->alias.smw_id"; |
| 203 | + $qobj->jointable = 'smw_ids'; |
| 204 | + $qobj->joinfield = "$qobj->alias.smw_id"; |
184 | 205 | $qobj->components = array($qid => "$qobj->alias.smw_id"); |
185 | 206 | $qobj->sortfields = $this->m_queries[$qid]->sortfields; |
186 | 207 | $this->m_queries[$rootid] = $qobj; |
187 | | - } else { // not such a common case, but it is worth avoiding the additional inner join here |
| 208 | + } else { // not such a common case, but worth avoiding the additional inner join: |
188 | 209 | $rootid = $qid; |
189 | 210 | } |
| 211 | + // include order conditions (may extend query if needed for sorting): |
| 212 | + $this->applyOrderConditions($query,$rootid); |
190 | 213 | |
191 | | - $this->applyOrderConditions($query,$rootid); // may extend query if needed for sorting |
| 214 | + //*** Now execute the computed query ***// |
192 | 215 | wfProfileIn('SMWSQLStore2Queries::executeMainQuery (SMW)'); |
193 | 216 | $this->executeQueries($this->m_queries[$rootid]); // execute query tree, resolve all dependencies |
194 | 217 | wfProfileOut('SMWSQLStore2Queries::executeMainQuery (SMW)'); |
— | — | @@ -269,8 +292,8 @@ |
270 | 293 | } |
271 | 294 | |
272 | 295 | /** |
273 | | - * Using a preprocessed internal query description referenced by $rootid, compute |
274 | | - * the proper result instance output for the given query. |
| 296 | + * Using a preprocessed internal query description referenced by $rootid, |
| 297 | + * compute the proper result instance output for the given query. |
275 | 298 | * @todo The SQL standard requires us to select all fields by which we sort, leading |
276 | 299 | * to wrong results regarding the given limit: the user expects limit to be applied to |
277 | 300 | * the number of distinct pages, but we can use DISTINCT only to whole rows. Thus, if |
— | — | @@ -316,18 +339,23 @@ |
317 | 340 | } |
318 | 341 | |
319 | 342 | /** |
320 | | - * Create a new SMWSQLStore2Query object that can be used to obtain results for |
321 | | - * the given description. The result is stored in $this->m_queries using a numeric |
322 | | - * key that is returned as a result of the function. Returns -1 if no query was |
323 | | - * created. |
| 343 | + * Create a new SMWSQLStore2Query object that can be used to obtain results |
| 344 | + * for the given description. The result is stored in $this->m_queries |
| 345 | + * using a numeric key that is returned as a result of the function. |
| 346 | + * Returns -1 if no query was created. |
| 347 | + * @todo The case of nominal classes (top-level SMWValueDescription) still |
| 348 | + * makes some assumptions about the table structure, especially about the |
| 349 | + * name of the joinfield (o_id). Better extend compileAttributeWhere to |
| 350 | + * deal with this case. |
324 | 351 | */ |
325 | 352 | protected function compileQueries(SMWDescription $description) { |
326 | 353 | $qid = SMWSQLStore2Query::$qnum; |
327 | 354 | $query = new SMWSQLStore2Query(); |
328 | 355 | if ($description instanceof SMWSomeProperty) { |
329 | 356 | $this->compilePropertyCondition($query, $description->getProperty(), $description->getDescription()); |
330 | | - if ($query->type == SMW_SQL2_NOQUERY) $qid = -1; // drop that right away |
331 | | - } elseif ($description instanceof SMWNamespaceDescription) { /// TODO: One instance of smw_ids on s_id always suffices (swm_id is KEY)! Doable in execution ... (PERFORMANCE) |
| 357 | + if ($query->type == SMW_SQL2_NOQUERY) $qid = -1; // compilation has set type to NOQUERY: drop condition |
| 358 | + } elseif ($description instanceof SMWNamespaceDescription) { |
| 359 | + /// TODO: One instance of smw_ids on s_id always suffices (swm_id is KEY)! Doable in execution ... (PERFORMANCE) |
332 | 360 | $query->jointable = 'smw_ids'; |
333 | 361 | $query->joinfield = "$query->alias.smw_id"; |
334 | 362 | $query->where = "$query->alias.smw_namespace=" . $this->m_dbs->addQuotes($description->getNamespace()); |
— | — | @@ -339,6 +367,7 @@ |
340 | 368 | $query->components[$sub] = true; |
341 | 369 | } |
342 | 370 | } |
| 371 | + if (count($query->components) == 0) $qid = -1; // all subconditions failed, drop this as well |
343 | 372 | } elseif ($description instanceof SMWClassDescription) { |
344 | 373 | $cqid = SMWSQLStore2Query::$qnum; |
345 | 374 | $cquery = new SMWSQLStore2Query(); |
— | — | @@ -396,9 +425,9 @@ |
397 | 426 | $may_be_computed = ($smwgQConceptCaching == CONCEPT_CACHE_NONE) || |
398 | 427 | ( ($smwgQConceptCaching == CONCEPT_CACHE_HARD) && ( (~(~($row->concept_features+0) | $smwgQFeatures)) == 0) && |
399 | 428 | ($smwgQMaxSize >= $row->concept_size) && ($smwgQMaxDepth >= $row->concept_depth)); |
400 | | - if ($row->cache_date && |
401 | | - ($row->cache_date > (strtotime("now") - $smwgQConceptCacheLifetime*60) || |
402 | | - !$may_be_computed)) { // cached concept, use cache unless it is dead and can be revived |
| 429 | + if ( $row->cache_date && |
| 430 | + ( ($row->cache_date > (strtotime("now") - $smwgQConceptCacheLifetime*60)) || |
| 431 | + !$may_be_computed ) ) { // cached concept, use cache unless it is dead and can be revived |
403 | 432 | $query->jointable = 'smw_conccache'; |
404 | 433 | $query->joinfield = "$query->alias.s_id"; |
405 | 434 | $query->where = "$query->alias.o_id=" . $this->m_dbs->addQuotes($cid); |
— | — | @@ -415,190 +444,219 @@ |
416 | 445 | } |
417 | 446 | } // else: no cache, no description (this may happen); treat like empty concept |
418 | 447 | } |
419 | | - } else { // (e.g. SMWThingDescription, SMWValueList is also treated elswhere) |
| 448 | + } else { // (e.g. SMWThingDescription) |
420 | 449 | $qid = -1; // no condition |
421 | 450 | } |
422 | | - if ($qid >= 0) { |
| 451 | + |
| 452 | + if ($qid >= 0) { // success, keep query object, propagate sortkeys from subqueries |
423 | 453 | $this->m_queries[$qid] = $query; |
424 | | - } |
425 | | - if ($query->type != SMW_SQL2_DISJUNCTION) { // sortkeys are killed by disjunctions (not all parts may have them), preprocessing might try to push disjunctions downwards to safe sortkey |
426 | | - foreach ($query->components as $cid => $field) { |
427 | | - $query->sortfields = array_merge($this->m_queries[$cid]->sortfields,$query->sortfields); |
| 454 | + if ($query->type != SMW_SQL2_DISJUNCTION) { // sortkeys are killed by disjunctions (not all parts may have them), |
| 455 | + // NOTE: preprocessing might try to push disjunctions downwards to safe sortkey, but this seems to be minor |
| 456 | + foreach ($query->components as $cid => $field) { |
| 457 | + $query->sortfields = array_merge($this->m_queries[$cid]->sortfields,$query->sortfields); |
| 458 | + } |
428 | 459 | } |
429 | 460 | } |
430 | 461 | return $qid; |
431 | 462 | } |
432 | 463 | |
433 | 464 | /** |
434 | | - * Modify the given query object to account for some property condition for the given property. |
435 | | - * The parameter $property may be a property object or an internal storage id. This is what makes |
436 | | - * this method useful: it can be used even with internal properties that have no MediaWiki Title. |
437 | | - * $typeid is set if property ids are used, since internal properties may not have a defined type. |
438 | | - * Some properties cannot be queried for; in this case, the query type is changed to SMW_SQL2_NOQUERY. |
439 | | - * Callers need to check for this. |
| 465 | + * Modify the given query object to account for some property condition for |
| 466 | + * the given property. If it is not possible to generate a query for the |
| 467 | + * given data, the query type is changed to SMW_SQL2_NOQUERY. Callers need |
| 468 | + * to check for this and discard the query in this case. |
| 469 | + * @todo Type-polymorphic properties are not taken into account yet. Needs |
| 470 | + * a getTypeID() function for descriptions that finds out if they apply to |
| 471 | + * wiki pages or to something else. |
| 472 | + * @todo Check if hierarchy queries work as expected. |
440 | 473 | */ |
441 | | - protected function compilePropertyCondition(&$query, $property, SMWDescription $valuedesc, $typeid=false) { |
442 | | - $query->joinfield = "$query->alias.s_id"; |
443 | | - $isinverse = false; |
444 | | - if ($property instanceof SMWPropertyValue) { |
| 474 | + protected function compilePropertyCondition($query, $property, SMWDescription $valuedesc) { |
| 475 | + $tableid = SMWSQLStore2::findPropertyTableID($property); |
| 476 | + if ($tableid == '') { // probably a type-polymorphic property |
| 477 | + $typeid = $valuedesc->getTypeID(); |
| 478 | + $tableid = SMWSQLStore2::findTypeTableID($typeid); |
| 479 | + } else { // normal property |
445 | 480 | $typeid = $property->getPropertyTypeID(); |
446 | | - $mode = SMWSQLStore2::getStorageMode($typeid); |
447 | | - $isinverse = $property->isInverse(); |
| 481 | + } |
| 482 | + if ($tableid == '') { // Still no table to query? Give up. |
| 483 | + $query->type = SMW_SQL2_NOQUERY; |
| 484 | + return; |
| 485 | + } |
| 486 | + $proptables = SMWSQLStore2::getPropertyTables(); |
| 487 | + $proptable = $proptables[$tableid]; |
| 488 | + if (!$proptable->idsubject) { // no queries with such tables (there is really no demand, as only redirects are affected) |
| 489 | + $query->type = SMW_SQL2_NOQUERY; |
| 490 | + return; |
| 491 | + } |
| 492 | + list($sig,$valueindex,$labelindex) = SMWSQLStore2::getTypeSignature($typeid); |
| 493 | + $sortkey = $property->getDBkey(); /// TODO: strictly speaking, the DB key is not what we want here, since sortkey is based on a "wiki value" |
| 494 | + |
| 495 | + //*** Basic settings: table, joinfield, and objectfields ***// |
| 496 | + $query->jointable = $proptable->name; |
| 497 | + if ($property->isInverse()) { // see if we can support inverses by inverting the proptable data |
| 498 | + if ( (count($proptable->objectfields) == 1) && (reset($proptable->objectfields) == 'p') ) { |
| 499 | + $query->joinfield = $query->alias . '.' . reset(array_keys($proptable->objectfields)); |
| 500 | + $objectfields = array( 's_id' => 'p'); |
| 501 | + $valueindex = $labelindex = 3; // should normally not change, but let's be strict |
| 502 | + } else { // no inverses supported for this property, stop here |
| 503 | + $query->type = SMW_SQL2_NOQUERY; |
| 504 | + return; |
| 505 | + } |
| 506 | + } else { // normal forward property |
| 507 | + $query->joinfield = "{$query->alias}.s_id"; |
| 508 | + $objectfields = $proptable->objectfields; |
| 509 | + } |
| 510 | + |
| 511 | + //*** Add conditions for selecting rows for this property, maybe with a hierarchy ***// |
| 512 | + if ($proptable->fixedproperty == false) { |
448 | 513 | $pid = $this->m_store->getSMWPropertyID($property); |
449 | | - $sortkey = $property->getDBkey(); /// TODO: strictly speaking, the DB key is not what we want here, since sortkey is based on a "wiki value" |
450 | | - if ( ($mode != SMW_SQL2_SUBS2) && ($mode != SMW_SQL2_SUBP2) ) { // also make property hierarchy (though not for all properties) |
| 514 | + if ( !$property->getPropertyID() || ($property->getPropertyTypeID() != '__err') ) { |
| 515 | + // also make property hierarchy (may or may not be executed later on) |
| 516 | + // exclude type-polymorphic properties _1, _2, ... (2nd check above suffices, but 1st is faster to check) |
| 517 | + // we could also exclude other cases here, if desired |
451 | 518 | $pqid = SMWSQLStore2Query::$qnum; |
452 | 519 | $pquery = new SMWSQLStore2Query(); |
453 | 520 | $pquery->type = SMW_SQL2_PROP_HIERARCHY; |
454 | 521 | $pquery->joinfield = array($pid); |
455 | | - $query->components[$pqid] = "$query->alias.p_id"; |
| 522 | + $query->components[$pqid] = "{$query->alias}.p_id"; |
456 | 523 | $this->m_queries[$pqid] = $pquery; |
| 524 | + } else { |
| 525 | + $query->where = "{$query->alias}.p_id=" . $this->m_dbs->addQuotes($pid); |
457 | 526 | } |
458 | | - } else { |
459 | | - $pid = $property; |
460 | | - $sortkey = false; |
461 | | - $mode = SMWSQLStore2::getStorageMode($typeid); |
462 | | - if ( ($mode != SMW_SQL2_SUBS2) && ($mode != SMW_SQL2_SUBP2) ) { // no property hierarchy, but normal query (not for all properties) |
463 | | - $query->where = "$query->alias.p_id=" . $this->m_dbs->addQuotes($pid); |
| 527 | + } // else: no property column, no hierarchy queries |
| 528 | + |
| 529 | + //*** Add conditions on the value of the property ***// |
| 530 | + if ( (count($objectfields) == 1) && (reset($objectfields) == 'p') ) { // page description, process like main query |
| 531 | + $sub = $this->compileQueries($valuedesc); |
| 532 | + $objectfield = reset(array_keys($objectfields)); |
| 533 | + if ($sub >= 0) { |
| 534 | + $query->components[$sub] = "{$query->alias}.{$objectfield}"; |
464 | 535 | } |
| 536 | + } else { // non-page value description; expressive features mainly based on value |
| 537 | + $this->compileAttributeWhere($query,$valuedesc,$proptable,$valueindex); |
| 538 | + // (no need to pass on $objectfields since they are just as in $proptable in this case) |
465 | 539 | } |
466 | | -// $mode = SMWSQLStore2::getStorageMode($typeid); |
467 | | - $sortfield = ''; // used if we should sort by this property |
468 | | - switch ($mode) { |
469 | | - case SMW_SQL2_RELS2: case SMW_SQL2_SUBS2: case SMW_SQL2_SUBP2: // subconditions as subqueries (compiled) |
470 | | - if ($isinverse) { $s='o'; $o='s'; } else { $s='s'; $o='o'; } |
471 | | - $query->joinfield = "$query->alias.$s" . "_id"; |
472 | | - $query->jointable = ($mode==SMW_SQL2_RELS2)?'smw_rels2': |
473 | | - ( ($mode==SMW_SQL2_SUBS2)?'smw_subs2':'smw_subp2' ); |
474 | | - $sub = $this->compileQueries($valuedesc); |
475 | | - if ($sub >= 0) { |
476 | | - $query->components[$sub] = "$query->alias.$o" . "_id"; |
| 540 | + |
| 541 | + //*** Incorporate ordering if desired ***// |
| 542 | + if ( ($valueindex >= 0) && array_key_exists($sortkey, $this->m_sortkeys) ) { |
| 543 | + // This code might be overly general: it supports datatypes of arbitrary signatures |
| 544 | + // and valueindex (sortkeys). It can even order pages by something other than their |
| 545 | + // sortkey (e.g. by their namespace?!), and it can handle values consisting of a page |
| 546 | + // and some more data fields before or after. Supporting pages in this way requires us |
| 547 | + // to iterate over the table fields since one page corresponds to four values in a |
| 548 | + // type's signature. Thankfully, signatures are short so this iteration is not notable. |
| 549 | + $fieldname = $smwidjoinfield = false; |
| 550 | + $this->getDBFieldsForDVIndex($objectfields,$valueindex,$fieldname,$smwidjoinfield); |
| 551 | + if ($fieldname) { |
| 552 | + if ($smwjoinfield) { |
| 553 | + /// TODO: is this smw_ids possibly duplicated in the query? Can we prevent that? (PERFORMANCE) |
| 554 | + $query->from = ' INNER JOIN ' . $this->m_dbs->tableName('smw_ids') . |
| 555 | + " AS ids{$query->alias} ON ids{$query->alias}.smw_id={$query->alias}.{$smwidjoinfield}"; |
| 556 | + $query->sortfields[$sortkey] = "ids{$query->alias}.{$fieldname}"; |
| 557 | + } else { |
| 558 | + $query->sortfields[$sortkey] = "{$query->alias}.{$fieldname}"; |
477 | 559 | } |
478 | | - if ( $sortkey && array_key_exists($sortkey, $this->m_sortkeys) ) { |
479 | | - $query->from = ' INNER JOIN ' . $this->m_dbs->tableName('smw_ids') . " AS ids$query->alias ON ids$query->alias.smw_id=$query->alias.$o" . "_id"; |
480 | | - $sortfield = "ids$query->alias.smw_title"; /// TODO: as below, smw_ids here is possibly duplicated! Can we prevent that? (PERFORMANCE) |
| 560 | + } |
| 561 | + } |
| 562 | + } |
| 563 | + |
| 564 | + /** |
| 565 | + * Helper function for matching an index that refers to the DB keys (and |
| 566 | + * thus signature) of a datatype to the database fields of a fitting |
| 567 | + * property table (the objectfields array of which is given). |
| 568 | + * The $fieldname is set call-by-ref, where the parameter $smwidjoinfield |
| 569 | + * is set to the field of $objectfields on which smw_ids.smw_id needs to |
| 570 | + * be joined if $smwidjoinfield refers to a field in smw_ids. This might |
| 571 | + * be needed for page-type values. If the value is not in smw_ids, then |
| 572 | + * $fieldname refers to $objectfields and $smwidjoinfield is false. If the |
| 573 | + * given index could not be matched, $fieldname is false. |
| 574 | + */ |
| 575 | + protected function getDBFieldsForDVIndex($objectfields, $index, &$fieldname, &$smwidjoinfield) { |
| 576 | + $curindex = 0; |
| 577 | + foreach ($objectfields as $fname => $ftype) { |
| 578 | + if ($ftype == 'p') { // special treatment since "p" consists of 4 fields that are kept in smw_ids |
| 579 | + if ($curindex+4 >= $index) { |
| 580 | + $idfieldnames = array('smw_title','smw_namespace','smw_iw','smw_sortkey'); |
| 581 | + $smwidjoinfield = $fname; |
| 582 | + $fieldname = $idfieldnames[$index-$curindex]; |
| 583 | + return; |
481 | 584 | } |
482 | | - break; |
483 | | - case SMW_SQL2_NARY2: |
484 | | - if ($isinverse) { // empty query -- inverses not supported here |
485 | | - $query->joinfield = ''; |
486 | | - break; |
487 | | - } |
488 | | - $query->jointable = 'smw_rels2'; |
489 | | - if ($valuedesc instanceof SMWValueList) { // anything else is ignored! |
490 | | - $typevalue = $property->getTypesValue(); |
491 | | - $typelabels = $typevalue->getTypeLabels(); |
492 | | - reset($typelabels); |
493 | | - $subqid = SMWSQLStore2Query::$qnum; |
494 | | - $subquery = new SMWSQLStore2Query(); |
495 | | - $subquery->type = SMW_SQL2_CONJUNCTION; |
496 | | - $query->components[$subqid] = "$query->alias.o_id"; |
497 | | - $this->m_queries[$subqid] = $subquery; |
498 | | - for ($i=0; $i<$valuedesc->getCount(); $i++) { |
499 | | - $desc = $valuedesc->getDescription($i); |
500 | | - if ($desc !== null) { |
501 | | - $stypeid = SMWDataValueFactory::findTypeID(current($typelabels)); |
502 | | - $valpid = $this->m_store->getSMWPageID(strval($i),SMW_NS_PROPERTY,SMW_SQL2_SMWIW); |
503 | | - $valqid = SMWSQLStore2Query::$qnum; |
504 | | - $valquery = new SMWSQLStore2Query(); |
505 | | - $this->compilePropertyCondition($valquery, $valpid, $desc, $stypeid); |
506 | | - if ($valquery->type != SMW_SQL2_NOQUERY) { |
507 | | - $subquery->components[$valqid] = true; |
508 | | - $this->m_queries[$valqid] = $valquery; |
509 | | - } |
510 | | - } |
511 | | - next($typelabels); |
512 | | - } |
513 | | - } |
514 | | - break; |
515 | | - case SMW_SQL2_TEXT2: // no subconditions |
516 | | - if ($isinverse) { // empty query -- inverses not supported here |
517 | | - $query->joinfield = ''; |
518 | | - break; |
519 | | - } |
520 | | - $query->jointable = 'smw_text2'; |
521 | | - break; |
522 | | - case SMW_SQL2_ATTS2: case SMW_SQL2_SPEC2: // subquery only conj/disj of values, compile to single "where" |
523 | | - if ($isinverse) { // empty query -- inverses not supported here |
524 | | - $query->joinfield = ''; |
525 | | - break; |
526 | | - } |
527 | | - $query->jointable = ($mode==SMW_SQL2_ATTS2)?'smw_atts2':'smw_spec2'; |
528 | | - $aw = $this->compileAttributeWhere($valuedesc,"$query->alias"); |
529 | | - if ($aw != '') { |
530 | | - $query->where .= ($query->where?' AND ':'') . $aw; |
531 | | - } |
532 | | - if ( $sortkey && array_key_exists($sortkey, $this->m_sortkeys) ) { |
533 | | - if ($mode==SMW_SQL2_ATTS2) { |
534 | | - $sortfield = "$query->alias." . (SMWDataValueFactory::newTypeIDValue($typeid)->isNumeric()?'value_num':'value_xsd'); |
535 | | - } else { |
536 | | - $sortfield = "$query->alias.value_string"; |
537 | | - } |
538 | | - } |
539 | | - break; |
540 | | - default: // drop this query |
541 | | - $query->type = SMW_SQL2_NOQUERY; |
542 | | - $sortfield = false; |
| 585 | + $curindex += 3; |
| 586 | + } elseif ($curindex == $index) { |
| 587 | + $smwidjoinfield = false; |
| 588 | + $fieldname = $fname; |
| 589 | + return; |
| 590 | + } |
| 591 | + $curindex++; |
543 | 592 | } |
544 | | - if ($sortfield) { |
545 | | - $query->sortfields[$sortkey] = $sortfield; |
546 | | - } |
| 593 | + $fieldname = false; // nothing found, maybe index too high or too low |
547 | 594 | } |
548 | 595 | |
549 | 596 | /** |
550 | 597 | * Given an SMWDescription that is just a conjunction or disjunction of |
551 | | - * SMWValueDescription objects, create a plain WHERE condition string for it. |
| 598 | + * SMWValueDescription objects, create and return a plain WHERE condition |
| 599 | + * string for it. |
| 600 | + * @bug The geo distance code is not secure. Disabled for now. |
552 | 601 | */ |
553 | | - protected function compileAttributeWhere(SMWDescription $description, $jointable) { |
| 602 | + protected function compileAttributeWhere($query, SMWDescription $description, $proptable, $valueindex, $operator = 'AND') { |
| 603 | + $where = ''; |
554 | 604 | if ($description instanceof SMWValueDescription) { |
555 | 605 | $dv = $description->getDatavalue(); |
556 | | - if (SMWSQLStore2::getStorageMode($dv->getTypeID()) == SMW_SQL2_SPEC2) { |
557 | | - $keys = $dv->getDBkeys(); |
558 | | - $value = $keys[0]; |
559 | | - $field = "$jointable.value_string"; |
560 | | - } else { //should be SMW_SQL2_ATTS2 |
561 | | - if ($dv->isNumeric()) { |
562 | | - $value = $dv->getNumericValue(); |
563 | | - $field = "$jointable.value_num"; |
564 | | - } else { |
565 | | - $keys = $dv->getDBkeys(); |
566 | | - $value = $keys[0]; |
567 | | - $field = "$jointable.value_xsd"; |
| 606 | + $keys = $dv->getDBkeys(); |
| 607 | + if ( ($valueindex >= 0) && ($description->getComparator() != SMW_CMP_EQ) ) { // try comparison based on value field and comparator |
| 608 | + // find field name for comparison |
| 609 | + $fieldname = $smwidjoinfield = false; |
| 610 | + $this->getDBFieldsForDVIndex($proptable->objectfields,$valueindex,$fieldname,$smwidjoinfield); |
| 611 | + if ($fieldname && !$smwidjoinfield ) { // do not support smw_id joined data for now |
| 612 | + $comp = ''; |
| 613 | + $fieldtype = $proptable->objectfields[$fieldname]; |
| 614 | + $value = $keys[$valueindex]; |
| 615 | + switch ($description->getComparator()) { |
| 616 | + case SMW_CMP_LEQ: $comp = '<='; break; |
| 617 | + case SMW_CMP_GEQ: $comp = '>='; break; |
| 618 | + case SMW_CMP_NEQ: $comp = '!='; break; |
| 619 | + case SMW_CMP_LIKE: |
| 620 | + if ($dv->getTypeID() == '_geo') { ///FIXME Insecure code that uses DB contents unescaped in SQL. Disabled for now. |
| 621 | +// $comp = '<='; |
| 622 | +// $geoarray = explode(",", $value); |
| 623 | +// if ((count($geoarray) == 2) && ($geoarray[0] != '') && ($geoarray[1] != '')) { |
| 624 | +// $field = "ROUND(((ACOS( SIN($geoarray[0] * PI()/180 ) * SIN(SUBSTRING_INDEX($field, ',',1) * PI()/180 ) + COS($geoarray[0] * PI()/180 ) * COS(SUBSTRING_INDEX($field, ',',1) * PI()/180 ) * COS(($geoarray[1] - SUBSTRING_INDEX($field, ',',-1)) * PI()/180))*180/PI())*60*1.1515),6)"; |
| 625 | +// } |
| 626 | +// $value = $this->m_distance; |
| 627 | + } elseif ($fieldtype == 't') { // string data allows pattern matches |
| 628 | + $comp = ' LIKE '; |
| 629 | + $value = str_replace(array('%', '_', '*', '?'), array('\%', '\_', '%', '_'), $value); // translate pattern |
| 630 | + } |
| 631 | + break; |
| 632 | + } |
| 633 | + if ( ($comp != '') && ($fieldtype != 'l') ) { |
| 634 | + $where = "{$query->alias}.{$fieldname}{$comp}" . $this->m_dbs->addQuotes($value); |
| 635 | + } |
568 | 636 | } |
569 | 637 | } |
570 | | - switch ($description->getComparator()) { |
571 | | - case SMW_CMP_LEQ: $comp = '<='; break; |
572 | | - case SMW_CMP_GEQ: $comp = '>='; break; |
573 | | - case SMW_CMP_NEQ: $comp = '!='; break; |
574 | | - case SMW_CMP_LIKE: |
575 | | - if ($dv->getTypeID() == '_str') { |
576 | | - $comp = ' LIKE '; |
577 | | - $value = str_replace(array('%', '_', '*', '?'), array('\%', '\_', '%', '_'), $value); |
578 | | - } elseif ($dv->getTypeID() == '_geo') { |
579 | | - $comp = '<='; |
580 | | - $geoarray = explode(",", $value); |
581 | | - if ((count($geoarray) == 2) && ($geoarray[0] != '') && ($geoarray[1] != '')) { |
582 | | - $field = "ROUND(((ACOS( SIN($geoarray[0] * PI()/180 ) * SIN(SUBSTRING_INDEX($field, ',',1) * PI()/180 ) + COS($geoarray[0] * PI()/180 ) * COS(SUBSTRING_INDEX($field, ',',1) * PI()/180 ) * COS(($geoarray[1] - SUBSTRING_INDEX($field, ',',-1)) * PI()/180))*180/PI())*60*1.1515),6)"; |
583 | | - } |
584 | | - $value = $this->m_distance; |
585 | | - } else { // LIKE only supported for strings and coordinates |
586 | | - $comp = '='; |
| 638 | + |
| 639 | + if ($where == '') { // comparators did not apply; match all fields |
| 640 | + $i = 0; |
| 641 | + foreach ($proptable->objectfields as $fname => $ftype) { |
| 642 | + if ($i>=count($keys)) break; |
| 643 | + if ($ftype == 'p') { // Special case: page id, resolve this in advance |
| 644 | + $oid = $this->getSMWPageID($keys[$i],$keys[$i+1],$keys[$i+2]); |
| 645 | + $i += 3; // skip these additional values (sortkey not needed here) |
| 646 | + $where .= ($where?' AND ':'') . "{$query->alias}.$fname=" . $this->m_dbs->addQuotes($oid); |
| 647 | + } elseif ($ftype != 'l') { // plain value, but not a text blob |
| 648 | + $where .= ($where?' AND ':'') . "{$query->alias}.$fname=" . $this->m_dbs->addQuotes($keys[$i]); |
587 | 649 | } |
588 | | - break; |
589 | | - case SMW_CMP_EQ: default: $comp = '='; break; |
| 650 | + $i++; |
| 651 | + } |
590 | 652 | } |
591 | | - $result = "$field$comp" . $this->m_dbs->addQuotes($value); |
592 | 653 | } elseif ( ($description instanceof SMWConjunction) || ($description instanceof SMWDisjunction) ) { |
593 | | - $op = ($description instanceof SMWConjunction) ? ' AND ' : ' OR '; |
594 | | - $result = ''; |
| 654 | + $op = ($description instanceof SMWConjunction) ? 'AND' : 'OR'; |
595 | 655 | foreach ($description->getDescriptions() as $subdesc) { |
596 | | - $result= $result . ( $result!=''?$op:'' ) . $this->compileAttributeWhere($subdesc,$jointable); |
| 656 | + //$where .= ($where!=''?$op:'') . |
| 657 | + $this->compileAttributeWhere($query,$subdesc,$proptable,$valueindex,$op); |
597 | 658 | } |
598 | | - $result = "($result)"; |
599 | | - } else { |
600 | | - $result = ''; |
601 | 659 | } |
602 | | - return $result; |
| 660 | + if ($where != '') $query->where .= ($query->where?" $operator ":'') . "($where)"; |
603 | 661 | } |
604 | 662 | |
605 | 663 | /** |