r60784 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r60783‎ | r60784 | r60785 >
Date:08:35, 7 January 2010
Author:mkroetzsch
Status:deferred
Tags:
Comment:
Major rewrite of SQL store: less lines of code, less bugs, more features.

Especially, custom code for n-ary properties was replaced by a new generic support for "container" properties which also encompass "internal objects" as proposed by some extensions.

Also, the parts of the DB layout that are specific to the types of data that is stored is now is no longer hard-coded but governed by global data records, making way for future extensions and restrictions of the DB setup without changing the code. The store no longer needs to know all datatype classes to work properly, so datatype extensions now are better supported.

Various @todos remain, but all essential features are working as before.
Modified paths:
  • /trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2.php (modified) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2_Queries.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2.php
@@ -14,20 +14,28 @@
1515 define('SMW_SQL2_SMWPREDEFIW',':smw-preprop'); // virtual "interwiki prefix" marking predefined objects (non-movable)
1616 define('SMW_SQL2_SMWINTDEFIW',':smw-intprop'); // virtual "interwiki prefix" marking internal (invisible) predefined properties
1717
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;
3026
 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+ }
3133
 34+ public function getFieldSignature() {
 35+ return implode($this->objectfields,'');
 36+ }
 37+}
 38+
 39+
3240 /**
3341 * Storage access class for using the standard MediaWiki SQL database
3442 * for keeping semantic data.
@@ -53,6 +61,9 @@
5462 /// >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
5563 protected static $in_getSemanticData = 0;
5664
 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+
5768 /// Use pre-defined ids for Very Important Properties, avoiding frequent ID lookups for those
5869 private static $special_ids = array(
5970 '_TYPE' => 1,
@@ -69,54 +80,90 @@
7081 '_CONC' => 19,
7182 '_SF_DF' => 20, // Semantic Form's default form property
7283 '_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,
7491 );
7592
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)
98111 // 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)
113125 );
114126
 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+
115162 ///// Reading methods /////
116163
117 - function getSemanticData($subject, $filter = false) {
 164+ public function getSemanticData($subject, $filter = false) {
118165 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 ***//
121168 if ( $subject instanceof Title ) { ///TODO: can this still occur?
122169 $sid = $this->getSMWPageID($subject->getDBkey(),$subject->getNamespace(),$subject->getInterwiki());
123170 $svalue = SMWWikiPageValue::makePageFromTitle($subject);
@@ -134,37 +181,11 @@
135182 wfProfileOut("SMWSQLStore2::getSemanticData (SMW)");
136183 return isset($svalue)?(new SMWSemanticData($svalue)):null;
137184 }
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 ***//
157186 if (!array_key_exists($sid, $this->m_semdata)) { // new cache entry
158187 $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();
164189 }
165 -
166 - if ($tasks != 0) { // fetch DB handler only when really needed!
167 - $db =& wfGetDB( DB_SLAVE );
168 - }
169190 if ( (count($this->m_semdata) > 20) && (SMWSQLStore2::$in_getSemanticData == 1) ) {
170191 // prevent memory leak;
171192 // It is not so easy to find the sweet spot between cache size and performance gains (both memory and time),
@@ -172,170 +193,21 @@
173194 $this->m_semdata = array($sid => $this->m_semdata[$sid]);
174195 $this->m_sdstate = array($sid => $this->m_sdstate[$sid]);
175196 }
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);
239204 }
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;
272206 }
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));
328210 }
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;
340212 }
341213
342214 SMWSQLStore2::$in_getSemanticData--;
@@ -343,18 +215,12 @@
344216 return $this->m_semdata[$sid];
345217 }
346218
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 = '') {
352220 wfProfileIn("SMWSQLStore2::getPropertyValues (SMW)");
353221 if ($property->isInverse()) { // inverses are working differently
354222 $noninverse = clone $property;
355223 $noninverse->setInverse(false);
356224 $result = $this->getPropertySubjects($noninverse,$subject,$requestoptions);
357 - wfProfileOut("SMWSQLStore2::getPropertyValues (SMW)");
358 - return $result;
359225 } elseif ($subject !== null) { // subject given, use semantic data cache:
360226 $sd = $this->getSemanticData($subject,array($property->getPropertyTypeID()));
361227 $result = $this->applyRequestOptions($sd->getPropertyValues($property),$requestoptions);
@@ -369,129 +235,153 @@
370236 }
371237 } else { // no subject given, get all values for the given property
372238 $pid = $this->getSMWPropertyID($property);
373 - if ( $pid == 0 ) {
 239+ $tableid = SMWSQLStore2::findPropertyTableID($property);
 240+ if ( ($pid == 0) || ($tableid == '') ) {
374241 wfProfileOut("SMWSQLStore2::getPropertyValues (SMW)");
375242 return array();
376243 }
377 - $db =& wfGetDB( DB_SLAVE );
 244+ $proptables = SMWSQLStore2::getPropertyTables();
 245+ $data = $this->fetchSemanticData($pid,$property,$proptables[$tableid],false,$requestoptions);
378246 $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;
489252 }
490253 }
491254 wfProfileOut("SMWSQLStore2::getPropertyValues (SMW)");
492255 return $result;
493256 }
494257
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) {
496386 /// TODO: should we share code with #ask query computation here? Just use queries?
497387 wfProfileIn("SMWSQLStore2::getPropertySubjects (SMW)");
498388 if ($property->isInverse()) { // inverses are working differently
@@ -501,194 +391,199 @@
502392 wfProfileOut("SMWSQLStore2::getPropertySubjects (SMW)");
503393 return $result;
504394 }
505 - $result = array();
 395+
 396+ //*** First build $select, $from, and $where for the DB query ***//
 397+ $select = $where = $from = '';
506398 $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());
510402 }
 403+ if ( ($pid == 0) || ($tableid == '') || ( ($value !== null) && (!$value->isValid()) ) ) {
 404+ return array();
 405+ }
 406+ $proptables = SMWSQLStore2::getPropertyTables();
 407+ $proptable = $proptables[$tableid];
511408 $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);
519420
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());
542456 }
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);
571471 }
572 - $db->freeResult($res);
573472 }
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]);
591484 }
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++;
610486 }
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;
617487 }
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;
633488 }
634489
635 - function getAllPropertySubjects(SMWPropertyValue $property, $requestoptions = null) {
 490+ public function getAllPropertySubjects(SMWPropertyValue $property, $requestoptions = null) {
636491 wfProfileIn("SMWSQLStore2::getAllPropertySubjects (SMW)");
637492 $result = $this->getPropertySubjects($property, null, $requestoptions);
638493 wfProfileOut("SMWSQLStore2::getAllPropertySubjects (SMW)");
639494 return $result;
640495 }
641496
642 - function getProperties($subject, $requestoptions = null) {
 497+ /**
 498+ * @todo Restrict this function to SMWWikiPageValue subjects.
 499+ */
 500+ public function getProperties($subject, $requestoptions = null) {
643501 wfProfileIn("SMWSQLStore2::getProperties (SMW)");
644502 $sid = $this->getSMWPageID($subject->getDBkey(), $subject->getNamespace(),$subject->getInterwiki());
645 - if ($sid == 0) {
 503+ if ($sid == 0) { // no id, no page, no properties
646504 wfProfileOut("SMWSQLStore2::getProperties (SMW)");
647505 return array();
648506 }
649507
650508 $db =& wfGetDB( DB_SLAVE );
651 - $sql = 's_id=' . $db->addQuotes($sid) . ' AND p_id=smw_id' . $this->getSQLConditions($requestoptions,'smw_sortkey','smw_sortkey');
652 -
653509 $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;
660525 }
 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+ }
661540 $db->freeResult($res);
662541 }
 542+ $result = $this->applyRequestOptions($result,$requestoptions); // apply options to overall result
663543 wfProfileOut("SMWSQLStore2::getProperties (SMW)");
664544 return $result;
665545 }
666546
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) {
671548 wfProfileIn("SMWSQLStore2::getInProperties (SMW)");
672549 $db =& wfGetDB( DB_SLAVE );
673550 $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+ }
683577 }
684578 $db->freeResult($res);
685579 }
 580+ $result = $this->applyRequestOptions($result,$requestoptions); // apply options to overall result
686581 wfProfileOut("SMWSQLStore2::getInProperties (SMW)");
687582 return $result;
688583 }
689584
690585 ///// Writing methods /////
691586
692 - function deleteSubject(Title $subject) {
 587+ public function deleteSubject(Title $subject) {
693588 wfProfileIn('SMWSQLStore2::deleteSubject (SMW)');
694589 $this->deleteSemanticData(SMWWikiPageValue::makePageFromTitle($subject));
695590 $this->updateRedirects($subject->getDBkey(), $subject->getNamespace()); // also delete redirects, may trigger update jobs!
@@ -705,7 +600,7 @@
706601 wfProfileOut('SMWSQLStore2::deleteSubject (SMW)');
707602 }
708603
709 - function updateData(SMWSemanticData $data) {
 604+ public function updateData(SMWSemanticData $data) {
710605 wfProfileIn("SMWSQLStore2::updateData (SMW)");
711606 $subject = $data->getSubject();
712607 $this->deleteSemanticData($subject);
@@ -720,137 +615,19 @@
721616 }
722617 // always make an ID (pages without ID cannot be in query results, not even in fixed value queries!):
723618 $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);
725621
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");
828625 }
829626
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 - }
852627 // Concepts are not just written but carefully updated,
853628 // preserving existing metadata (cache ...) for a concept:
854629 if ( $subject->getNamespace() == SMW_NS_CONCEPT ) {
 630+ $property = SMWPropertyValue::makeProperty('_CONC');
 631+ $concept_desc = end($data->getPropertyValues($property));
855632 if ( ($concept_desc !== null) && ($concept_desc->isValid()) ) {
856633 $up_conc2 = array(
857634 'concept_txt' => $concept_desc->getConceptText(),
@@ -882,79 +659,141 @@
883660 }
884661 }
885662
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
888666 wfProfileOut("SMWSQLStore2::updateData (SMW)");
889667 }
890668
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;
892758 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:
896760 $sid = $this->getSMWPageID($oldtitle->getDBkey(),$oldtitle->getNamespace(),'',false);
897 - $tid_c = $this->getSMWPageID($newtitle->getDBkey(),$newtitle->getNamespace(),'');
898761 $tid = $this->getSMWPageID($newtitle->getDBkey(),$newtitle->getNamespace(),'',false);
899 -
900762 $db =& wfGetDB( DB_MASTER );
901763
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');
909770 } 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(),'');
911772 } // at this point, $sid is the id of the target page (according to smw_ids)
912773 $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;
914780 /// 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.
921782 /// NOTE: this temporarily leaves existing redirects to oldtitle point to newtitle as well, which
922783 /// 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:
930791 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);
955793 }
 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());
956796 /// TODO: may not be optimal for the standard case that newtitle existed and redirected to oldtitle (PERFORMANCE)
957797 }
958 -
959798 wfProfileOut("SMWSQLStore2::changeTitle (SMW)");
960799 }
961800
@@ -972,30 +811,27 @@
973812
974813 ///// Special page functions /////
975814
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) {
977820 wfProfileIn("SMWSQLStore2::getPropertiesSpecial (SMW)");
978821 $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()
980832 if ($requestoptions->limit > 0) {
981 - $options .= ' LIMIT ' . $requestoptions->limit;
 833+ $query = $db->limitResult($query,$requestoptions->limit,($requestoptions->offset > 0 )?$requestoptions->offset:0);
982834 }
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');
1000836 $result = array();
1001837 while($row = $db->fetchObject($res)) {
1002838 $result[] = array(SMWPropertyValue::makeProperty($row->smw_title), $row->count);
@@ -1005,21 +841,21 @@
1006842 return $result;
1007843 }
1008844
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) {
1010853 global $wgDBtype;
1011854 wfProfileIn("SMWSQLStore2::getUnusedPropertiesSpecial (SMW)");
1012855 $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';
1023857
 858+ // we use a temporary table for executing this costly operation on the DB side
 859+ $smw_tmp_unusedprops = $db->tableName('smw_tmp_unusedprops');
1024860 if ($wgDBtype=='postgres') { // PostgresQL: no in-memory tables available
1025861 $sql = "CREATE OR REPLACE FUNCTION create_" . $smw_tmp_unusedprops . "() RETURNS void AS "
1026862 ."$$ "
@@ -1036,217 +872,239 @@
1037873 } else { // MySQL: use temporary in-memory table
1038874 $sql = "CREATE TEMPORARY TABLE " . $smw_tmp_unusedprops . "( title VARCHAR(255) ) TYPE=MEMORY";
1039875 }
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
1045890 }
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 );
1053902
 903+ $options = $this->getSQLOptions($requestoptions,'title');
 904+ $options['ORDER BY'] = 'title';
 905+ $res = $db->select($smw_tmp_unusedprops, 'title', '', $fname, $options);
1054906 $result = array();
1055907 while($row = $db->fetchObject($res)) {
1056908 $result[] = SMWPropertyValue::makeProperty($row->title);
1057909 }
1058910 $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);
1060913 wfProfileOut("SMWSQLStore2::getUnusedPropertiesSpecial (SMW)");
1061914 return $result;
1062915 }
1063916
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) {
1065923 global $smwgPDefaultType;
1066924 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)];
1089928 $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+ }
1092941 }
1093942 wfProfileOut("SMWSQLStore2::getWantedPropertiesSpecial (SMW)");
1094943 return $result;
1095944 }
1096945
1097 - function getStatistics() {
 946+ public function getStatistics() {
1098947 wfProfileIn('SMWSQLStore2::getStatistics (SMW)');
1099948 $db =& wfGetDB( DB_SLAVE );
1100949 $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');
1108966 $row = $db->fetchObject( $res );
1109 - $propuses += $row->count;
 967+ $result['PROPUSES'] += $row->count;
1110968 $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+ }
1114977 $db->freeResult( $res );
1115978 }
1116 - $result['PROPUSES'] = $propuses;
1117 - $result['USEDPROPS'] = $usedprops;
1118979
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 -
1124980 wfProfileOut('SMWSQLStore2::getStatistics (SMW)');
1125981 return $result;
1126982 }
1127983
1128984 ///// Setup store /////
1129985
1130 - function setup($verbose = true) {
1131 - global $wgDBtype;
 986+ public function setup($verbose = true) {
1132987 $this->reportProgress("Setting up standard database configuration for SMW ...\n\n",$verbose);
1133988 $this->reportProgress("Selected storage engine is \"SMWSQLStore2\" (or an extension thereof)\n\n",$verbose);
1134989 $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') );
11381023 $reportTo = $verbose?$this:null; // use $this to report back from static SMWSQLHelpers
11391024 // 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'));
11451034
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
11971036 if ( ($db->tableExists($smw_spec2)) && ($db->fieldExists($smw_spec2, 'sp_id', 'SMWSQLStore2::setup')) ) {
11981037 if ($wgDBtype=='postgres') {
11991038 $db->query("ALTER TABLE $smw_spec2 ALTER COLUMN sp_id RENAME TO p_id", 'SMWSQLStore2::setup');
12001039 } 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');
12021041 }
12031042 }
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);
12091043
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);
12141052
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);
12341069 SMWSQLHelpers::setupIndex($smw_conc2, array('s_id'), $db);
12351070
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+ }
12401091
12411092 $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;
12421102 $this->reportProgress("Setting up internal property indices ...\n",$verbose);
12431103 // 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));
12451105 if ($borderiw != SMW_SQL2_SMWBORDERIW) {
12461106 $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(
12511109 'smw_id' => 50,
12521110 'smw_title' => '',
12531111 'smw_namespace' => 0,
@@ -1257,8 +1115,8 @@
12581116
12591117 $this->reportProgress(" ",$verbose);
12601118 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);
12631121 }
12641122 $this->reportProgress("done\n",$verbose);
12651123 } else {
@@ -1268,17 +1126,13 @@
12691127 $this->reportProgress(" ... writing entries for internal properties.\n",$verbose);
12701128 foreach ( SMWSQLStore2::$special_ids as $prop => $id ) {
12711129 $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(
12761131 'smw_id' => $id,
12771132 'smw_title' => $p->getDBkey(),
12781133 'smw_namespace' => SMW_NS_PROPERTY,
12791134 'smw_iw' => $this->getPropertyInterwiki( $p ),
12801135 'smw_sortkey' => $p->getDBkey()
1281 - ),
1282 - 'SMW::setup'
 1136+ ), 'SMW::setup'
12831137 );
12841138 }
12851139 if( $wgDBtype == 'postgres' ) {
@@ -1288,16 +1142,16 @@
12891143 $db->query( "ALTER SEQUENCE smw_ids_smw_id_seq RESTART WITH {$max}", __METHOD__ );
12901144 }
12911145 $this->reportProgress("Internal properties initialised successfully.\n",$verbose);
1292 - return true;
12931146 }
12941147
1295 - function drop($verbose = true) {
 1148+ public function drop($verbose = true) {
12961149 global $wgDBtype;
12971150 $this->reportProgress("Deleting all database content and tables generated by SMW ...\n\n",$verbose);
12981151 $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+ }
13021156 foreach ($tables as $table) {
13031157 $name = $db->tableName($table);
13041158 $db->query('DROP TABLE' . ($wgDBtype=='postgres'?'':' IF EXISTS'). $name, 'SMWSQLStore2::drop');
@@ -1441,7 +1295,7 @@
14421296 * The parameter $valuecol defines the string name of the column to which
14431297 * sorting requests etc. are to be applied.
14441298 */
1445 - protected function getSQLOptions($requestoptions, $valuecol = null) {
 1299+ protected function getSQLOptions($requestoptions, $valuecol = '') {
14461300 $sql_options = array();
14471301 if ($requestoptions !== null) {
14481302 if ($requestoptions->limit > 0) {
@@ -1450,7 +1304,7 @@
14511305 if ($requestoptions->offset > 0) {
14521306 $sql_options['OFFSET'] = $requestoptions->offset;
14531307 }
1454 - if ( ($valuecol !== null) && ($requestoptions->sort) ) {
 1308+ if ( ($valuecol != '') && ($requestoptions->sort) ) {
14551309 $sql_options['ORDER BY'] = $requestoptions->ascending ? $valuecol : $valuecol . ' DESC';
14561310 }
14571311 }
@@ -1464,20 +1318,21 @@
14651319 * @param $requestoptions object with options
14661320 * @param $valuecol name of SQL column to which conditions apply
14671321 * @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
14681323 */
1469 - protected function getSQLConditions($requestoptions, $valuecol, $labelcol = null) {
 1324+ protected function getSQLConditions($requestoptions, $valuecol = '', $labelcol = '', $addand = true) {
14701325 $sql_conds = '';
14711326 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
14741329 if ($requestoptions->ascending) {
14751330 $op = $requestoptions->include_boundary?' >= ':' > ';
14761331 } else {
14771332 $op = $requestoptions->include_boundary?' <= ':' < ';
14781333 }
1479 - $sql_conds .= ' AND ' . $valuecol . $op . $db->addQuotes($requestoptions->boundary);
 1334+ $sql_conds .= ($addand?' AND ':'') . $valuecol . $op . $db->addQuotes($requestoptions->boundary);
14801335 }
1481 - if ($labelcol !== null) { // apply string conditions
 1336+ if ($labelcol != '') { // apply string conditions
14821337 foreach ($requestoptions->getStringConditions() as $strcond) {
14831338 $string = str_replace('_', '\_', $strcond->string);
14841339 switch ($strcond->condition) {
@@ -1485,7 +1340,7 @@
14861341 case SMWStringCondition::STRCOND_POST: $string = '%' . $string; break;
14871342 case SMWStringCondition::STRCOND_MID: $string = '%' . $string . '%'; break;
14881343 }
1489 - $sql_conds .= ' AND ' . $labelcol . ' LIKE ' . $db->addQuotes($string);
 1344+ $sql_conds .= (($addand || ($sql_conds!=''))?' AND ':'') . $labelcol . ' LIKE ' . $db->addQuotes($string);
14901345 }
14911346 }
14921347 }
@@ -1493,39 +1348,29 @@
14941349 }
14951350
14961351 /**
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.
15011357 */
15021358 protected function applyRequestOptions($data, $requestoptions) {
15031359 wfProfileIn("SMWSQLStore2::applyRequestOptions (SMW)");
1504 - $result = array();
1505 - $sortres = array();
1506 - $key = 0;
15071360 if ( (count($data) == 0) || ($requestoptions === null) ) {
15081361 wfProfileOut("SMWSQLStore2::applyRequestOptions (SMW)");
15091362 return $data;
15101363 }
 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;
15111370 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]:'';
15301375 if ($requestoptions->boundary !== null) { // apply value boundary
15311376 $strc = $numeric?0:strcmp($value,$requestoptions->boundary);
15321377 if ($requestoptions->ascending) {
@@ -1556,13 +1401,12 @@
15571402 }
15581403 }
15591404 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++;
15631408 }
15641409 }
15651410 if ($requestoptions->sort) {
1566 - // use last value of $numeric to indicate overall type
15671411 $flag = $numeric?SORT_NUMERIC:SORT_LOCALE_STRING;
15681412 if ($requestoptions->ascending) {
15691413 asort($sortres,$flag);
@@ -1600,31 +1444,92 @@
16011445 }
16021446
16031447 /**
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?
16071458 */
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)) {
16121461 $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+ }
16201500 }
 1501+ SMWSQLStore2::$property_table_ids[$typeid] = ''; // no matching table found
16211502 }
 1503+ return SMWSQLStore2::$property_table_ids[$typeid];
16221504 }
16231505
16241506 /**
 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+ /**
16251530 * 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.
16291534 */
16301535 public function getSMWPageID($title, $namespace, $iw, $canonical=true) {
16311536 $sort = '';
@@ -1632,8 +1537,11 @@
16331538 }
16341539
16351540 /**
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).
16381546 */
16391547 public function getSMWPageIDandSort($title, $namespace, $iw, &$sort, $canonical) {
16401548 global $smwgQEqualitySupport;
@@ -1650,54 +1558,45 @@
16511559 }
16521560 $db =& wfGetDB( DB_SLAVE );
16531561 $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));
16571566 if ($row = $db->fetchObject($res)) {
16581567 $id = $row->smw_id;
16591568 $sort = $row->smw_sortkey;
16601569 }
 1570+ $this->m_ids[ $canonical?$nkey:$ckey ] = $id; // unique id, make sure cache for canonical+non-cacnonical gets filled
16611571 } 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));
16631576 if ($row = $db->fetchObject($res)) {
1664 - $sort = $row->smw_sortkey;
16651577 $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 ) );
16691591 }
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);
16731596 }
16741597 }
16751598 }
16761599 $db->freeResult($res);
16771600
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 - }
17021601 $this->m_ids[$key] = $id;
17031602 wfProfileOut('SMWSQLStore2::getSMWPageID (SMW)');
17041603 return $id;
@@ -1705,13 +1604,13 @@
17061605
17071606 /**
17081607 * 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
17151613 * 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.
17161615 */
17171616 protected function makeSMWPageID($title, $namespace, $iw, $canonical=true, $sortkey = '') {
17181617 wfProfileIn('SMWSQLStore2::makeSMWPageID (SMW)');
@@ -1721,14 +1620,11 @@
17221621 $db =& wfGetDB( DB_MASTER );
17231622 $sortkey = $sortkey?$sortkey:(str_replace('_',' ',$title));
17241623 $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');
17331629 $id = $db->insertId();
17341630 $this->m_ids["$iw $namespace $title -"] = $id; // fill that cache, even if canonical was given
17351631 // This ID is also authorative for the canonical version.
@@ -1744,357 +1640,368 @@
17451641 }
17461642
17471643 /**
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).
17511648 */
17521649 private function getPropertyInterwiki(SMWPropertyValue $property) {
17531650 if ($property->isUserDefined()) {
17541651 return '';
1755 - } elseif ($property->isVisible()) {
1756 - return SMW_SQL2_SMWPREDEFIW;
17571652 } else {
1758 - return SMW_SQL2_SMWINTDEFIW;
 1653+ return $property->isVisible()?SMW_SQL2_SMWPREDEFIW:SMW_SQL2_SMWINTDEFIW;
17591654 }
17601655 }
17611656
17621657 /**
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.
17641660 */
17651661 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
17681664 } else {
17691665 return $this->getSMWPageID($property->getDBkey(),SMW_NS_PROPERTY,$this->getPropertyInterwiki($property),true);
17701666 }
17711667 }
17721668
17731669 /**
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.
17751672 */
17761673 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
17791676 } else {
17801677 return $this->makeSMWPageID($property->getDBkey(),SMW_NS_PROPERTY,$this->getPropertyInterwiki($property),true);
17811678 }
17821679 }
17831680
17841681 /**
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.
17891687 */
17901688 public function cacheSMWPageID($id, $title, $namespace, $iw) {
1791 - $real_iw = ($iw == SMW_SQL2_SMWREDIIW)?'':$iw;
17921689 $ckey = "$iw $namespace $title C";
17931690 $nkey = "$iw $namespace $title -";
17941691 if (count($this->m_ids)>1500) { // prevent memory leak in very long PHP runs
17951692 $this->m_ids = array();
17961693 }
17971694 $this->m_ids[$nkey] = $id;
1798 - if ($real_iw === $iw) {
 1695+ if ($iw != SMW_SQL2_SMWREDIIW) {
17991696 $this->m_ids[$ckey] = $id;
18001697 }
18011698 }
18021699
18031700 /**
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!
18121710 */
18131711 protected function makeSMWBnodeID($sid) {
18141712 $db =& wfGetDB( DB_MASTER );
1815 - $id = 0;
18161713 // 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);
18271718 // claim that bnode:
18281719 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 ) );
18411725 if ($db->affectedRows() == 0) { // Oops, someone was faster (collisions are possible here, no locks)
18421726 $id = 0; // fallback: make a new node (TODO: we could also repeat to try another ID)
18431727 }
18441728 }
18451729 // if no node was found yet, make a new one:
18461730 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' );
18531736 $id = $db->insertId();
18541737 }
18551738 return $id;
18561739 }
18571740
18581741 /**
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.
18641750 */
1865 - protected function moveID($curid, $targetid = 0) {
 1751+ protected function moveSMWPageID($curid, $targetid = 0) {
18661752 $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' );
18731756 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' );
18851763 $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' );
18971770 }
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+ }
19051774
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 );
19181798 }
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+ }
19271818 }
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+ }
19361832 }
19371833
19381834 /**
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.
19411837 */
1942 - public function deleteSemanticData(SMWWikiPageValue $subject) {
 1838+ protected function deleteSemanticData(SMWWikiPageValue $subject) {
19431839 $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';
19461841 $id = $this->getSMWPageID($subject->getDBkey(),$subject->getNamespace(),$subject->getInterwiki(),false);
19471842 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+ }
19571849 }
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 );
19651852 // ... and delete them as well
19661853 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+ }
19701859 }
19711860 $db->freeResult($res);
19721861 // 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 );
19791863 wfRunHooks('smwDeleteSemanticData', array($subject));
19801864 }
19811865
19821866 /**
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.
19911881 */
19921882 protected function updateRedirects($subject_t, $subject_ns, $curtarget_t='', $curtarget_ns=-1) {
19931883 global $smwgQEqualitySupport, $smwgEnableUpdateJobs;
 1884+ $fname = 'SMW::updateRedirects';
 1885+
 1886+ //*** First get id of subject, old redirect target, and current (new) redirect target ***//
19941887 $sid = $this->getSMWPageID($subject_t, $subject_ns, '', false); // find real id of subject, if any
19951888 /// 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
19961890 $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) );
20001892 $old_tid = ($row = $db->fetchObject($res))?$row->o_id:0; // real id of old target, if any
20011893 $db->freeResult($res);
2002 - $new_tid = $curtarget_t?($this->makeSMWPageID($curtarget_t, $curtarget_ns, '', false)):0; // real id of new target
20031894 /// NOTE: $old_tid and $new_tid both ignore further redirects, (intentionally) no redirect chains!
 1895+
20041896 if ($old_tid == $new_tid) { // no change, all happy
20051897 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) ***//
20071901 $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
20371911 $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);
20531923 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));
20561925 }
 1926+ $db->freeResult($res);
20571927 }
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);
20731935 }
20741936 }
20751937 }
 1938+ /// NOTE: we do not update the concept cache here; this remains an offline task
20761939 Job::batchInsert($jobs); ///NOTE: this only happens if $smwgEnableUpdateJobs was true above
20771940 }
20781941 }
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+ }
20871954 }
 1955+ $db->insert( 'smw_redi2', array('s_title'=>$subject_t, 's_namespace'=>$subject_ns, 'o_id'=>$new_tid), $fname);
20881956 $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.
20901960 $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);
20931963 }
20941964 }
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 ***//
20961966 unset($this->m_semdata[$sid]); unset($this->m_semdata[$new_tid]); unset($this->m_semdata[$old_tid]);
20971967 unset($this->m_sdstate[$sid]); unset($this->m_sdstate[$new_tid]); unset($this->m_sdstate[$old_tid]);
20981968 return ($new_tid==0)?$sid:$new_tid;
20991969 }
21001970
 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+
21012008 }
Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2_Queries.php
@@ -1,6 +1,6 @@
22 <?php
33 /**
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
55 * for avoiding twice the amount of code being required on every use of a simple storage function.
66 *
77 * @author Markus Krötzsch
@@ -47,21 +47,21 @@
4848
4949 /// Database slave to be used
5050 protected $m_dbs; /// TODO: should temporary tables be created on the master DB?
51 - /// Parent SMWSQLStore2
 51+ /// Parent SMWSQLStore2.
5252 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.
5454 protected $m_qmode;
55 - /// Array of generated query descriptions
 55+ /// Array of generated SMWSQLStore2Query query descriptions (index => object).
5656 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.
5858 protected $m_querylog = array();
5959 /**
60 - * Array of sorting requests ("Property_name" => "ASC"/"DESC". Used during query
 60+ * Array of sorting requests ("Property_name" => "ASC"/"DESC"). Used during query
6161 * processing (where these property names are searched while compiling the query
6262 * conditions).
6363 */
6464 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").
6666 protected $m_hierarchies = array();
6767 /// Local collection of error strings, passed on to callers if possible.
6868 protected $m_errors = array();
@@ -141,17 +141,38 @@
142142 }
143143
144144 /**
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.
146169 */
147170 public function getQueryResult(SMWQuery $query) {
148171 global $smwgIgnoreQueryErrors;
149172 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);
152174 /// NOTE: We currently check this only once since the below steps do not create further errors
153175 } 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);
156177 }
157178 $this->m_qmode = $query->querymode;
158179 $this->m_queries = array();
@@ -161,7 +182,8 @@
162183 $this->m_distance = $query->getDistance();
163184 SMWSQLStore2Query::$qnum = 0;
164185 $this->m_sortkeys = $query->sortkeys;
165 - // build query dependency tree:
 186+
 187+ //*** First compute abstract representation of the query (compilation) ***//
166188 wfProfileIn('SMWSQLStore2Queries::compileMainQuery (SMW)');
167189 $qid = $this->compileQueries($query->getDescription()); // compile query, build query "plan"
168190 wfProfileOut('SMWSQLStore2Queries::compileMainQuery (SMW)');
@@ -173,21 +195,22 @@
174196 $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);
175197 $this->m_queries[$qid] = $q;
176198 }
177 - // append query to root:
178199 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):
180201 $rootid = SMWSQLStore2Query::$qnum;
181202 $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";
184205 $qobj->components = array($qid => "$qobj->alias.smw_id");
185206 $qobj->sortfields = $this->m_queries[$qid]->sortfields;
186207 $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:
188209 $rootid = $qid;
189210 }
 211+ // include order conditions (may extend query if needed for sorting):
 212+ $this->applyOrderConditions($query,$rootid);
190213
191 - $this->applyOrderConditions($query,$rootid); // may extend query if needed for sorting
 214+ //*** Now execute the computed query ***//
192215 wfProfileIn('SMWSQLStore2Queries::executeMainQuery (SMW)');
193216 $this->executeQueries($this->m_queries[$rootid]); // execute query tree, resolve all dependencies
194217 wfProfileOut('SMWSQLStore2Queries::executeMainQuery (SMW)');
@@ -269,8 +292,8 @@
270293 }
271294
272295 /**
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.
275298 * @todo The SQL standard requires us to select all fields by which we sort, leading
276299 * to wrong results regarding the given limit: the user expects limit to be applied to
277300 * the number of distinct pages, but we can use DISTINCT only to whole rows. Thus, if
@@ -316,18 +339,23 @@
317340 }
318341
319342 /**
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.
324351 */
325352 protected function compileQueries(SMWDescription $description) {
326353 $qid = SMWSQLStore2Query::$qnum;
327354 $query = new SMWSQLStore2Query();
328355 if ($description instanceof SMWSomeProperty) {
329356 $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)
332360 $query->jointable = 'smw_ids';
333361 $query->joinfield = "$query->alias.smw_id";
334362 $query->where = "$query->alias.smw_namespace=" . $this->m_dbs->addQuotes($description->getNamespace());
@@ -339,6 +367,7 @@
340368 $query->components[$sub] = true;
341369 }
342370 }
 371+ if (count($query->components) == 0) $qid = -1; // all subconditions failed, drop this as well
343372 } elseif ($description instanceof SMWClassDescription) {
344373 $cqid = SMWSQLStore2Query::$qnum;
345374 $cquery = new SMWSQLStore2Query();
@@ -396,9 +425,9 @@
397426 $may_be_computed = ($smwgQConceptCaching == CONCEPT_CACHE_NONE) ||
398427 ( ($smwgQConceptCaching == CONCEPT_CACHE_HARD) && ( (~(~($row->concept_features+0) | $smwgQFeatures)) == 0) &&
399428 ($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
403432 $query->jointable = 'smw_conccache';
404433 $query->joinfield = "$query->alias.s_id";
405434 $query->where = "$query->alias.o_id=" . $this->m_dbs->addQuotes($cid);
@@ -415,190 +444,219 @@
416445 }
417446 } // else: no cache, no description (this may happen); treat like empty concept
418447 }
419 - } else { // (e.g. SMWThingDescription, SMWValueList is also treated elswhere)
 448+ } else { // (e.g. SMWThingDescription)
420449 $qid = -1; // no condition
421450 }
422 - if ($qid >= 0) {
 451+
 452+ if ($qid >= 0) { // success, keep query object, propagate sortkeys from subqueries
423453 $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+ }
428459 }
429460 }
430461 return $qid;
431462 }
432463
433464 /**
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.
440473 */
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
445480 $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) {
448513 $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
451518 $pqid = SMWSQLStore2Query::$qnum;
452519 $pquery = new SMWSQLStore2Query();
453520 $pquery->type = SMW_SQL2_PROP_HIERARCHY;
454521 $pquery->joinfield = array($pid);
455 - $query->components[$pqid] = "$query->alias.p_id";
 522+ $query->components[$pqid] = "{$query->alias}.p_id";
456523 $this->m_queries[$pqid] = $pquery;
 524+ } else {
 525+ $query->where = "{$query->alias}.p_id=" . $this->m_dbs->addQuotes($pid);
457526 }
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}";
464535 }
 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)
465539 }
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}";
477559 }
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;
481584 }
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++;
543592 }
544 - if ($sortfield) {
545 - $query->sortfields[$sortkey] = $sortfield;
546 - }
 593+ $fieldname = false; // nothing found, maybe index too high or too low
547594 }
548595
549596 /**
550597 * 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.
552601 */
553 - protected function compileAttributeWhere(SMWDescription $description, $jointable) {
 602+ protected function compileAttributeWhere($query, SMWDescription $description, $proptable, $valueindex, $operator = 'AND') {
 603+ $where = '';
554604 if ($description instanceof SMWValueDescription) {
555605 $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+ }
568636 }
569637 }
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]);
587649 }
588 - break;
589 - case SMW_CMP_EQ: default: $comp = '='; break;
 650+ $i++;
 651+ }
590652 }
591 - $result = "$field$comp" . $this->m_dbs->addQuotes($value);
592653 } elseif ( ($description instanceof SMWConjunction) || ($description instanceof SMWDisjunction) ) {
593 - $op = ($description instanceof SMWConjunction) ? ' AND ' : ' OR ';
594 - $result = '';
 654+ $op = ($description instanceof SMWConjunction) ? 'AND' : 'OR';
595655 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);
597658 }
598 - $result = "($result)";
599 - } else {
600 - $result = '';
601659 }
602 - return $result;
 660+ if ($where != '') $query->where .= ($query->where?" $operator ":'') . "($where)";
603661 }
604662
605663 /**

Status & tagging log