Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
MatrixDALBaker.inc
1 <?php
18 require_once SQ_LIB_PATH.'/DAL/DALBaker.inc';
19 require_once SQ_LIB_PATH.'/FileSystem/FileSystem.inc';
20 require_once SQ_LIB_PATH.'/XML/XML.inc';
21 
45 class MatrixDALBaker extends DALBaker
46 {
47 
48 
53  function __construct()
54  {
55  parent::__construct();
56 
57  }//end constructor
58 
59 
72  public static function addCoreQueries()
73  {
74  $filename = SQ_SYSTEM_ROOT.'/core/db/queries.xml';
75 
76  if (file_exists($filename)) {
77  self::processQueriesFile('core', $filename);
78  return TRUE;
79  }
80 
81 
82  return FALSE;
83 
84  }//end addPackageQueries()
85 
86 
101  public static function addPackageQueries($package)
102  {
103  $packages = $GLOBALS['SQ_SYSTEM']->getInstalledPackages();
104  if (in_array($package, $packages)) {
105  throw new DALBakerException('Unable to install queries for package "'.$package.'"; the package must be installed first.');
106  }
107 
108  if ($package === 'core') {
109  $filename = SQ_CORE_PACKAGE_PATH.'/queries.xml';
110  } else {
111  $filename = SQ_PACKAGES_PATH.'/'.$package.'/queries.xml';
112  }
113 
114  if (file_exists($filename)) {
115  self::processQueriesFile($package.'_package', $filename);
116  return TRUE;
117  }
118 
119  return FALSE;
120 
121  }//end addPackageQueries()
122 
123 
138  public static function addAssetTypeQueries($assetType)
139  {
140  if (!$GLOBALS['SQ_SYSTEM']->am->installed($assetType)) {
141  throw new DALBakerException('Unable to install queries for asset "'.$assetType.'"; the asset must be installed first.');
142  }
143 
144  $dir = $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($assetType, 'dir');
145  $filename = SQ_SYSTEM_ROOT.'/'.$dir.'/db/queries.xml';
146 
147  if (file_exists($filename)) {
148  self::processQueriesFile($assetType, $filename);
149  return TRUE;
150  }
151 
152  return FALSE;
153 
154  }//end addAssetTypeQueries()
155 
156 
170  public static function bakeQueriesFile($systemName)
171  {
172  if (trim($systemName) === '') {
173  throw new DALBakerException('System name is empty');
174  }
175 
176  // Write the file.
177  $ovenDir = SQ_LIB_PATH.'/DAL/Oven/'.$systemName;
178 
179  if (file_exists($ovenDir) === FALSE) {
180  mkdir($ovenDir);
181  }
182 
183  $fileName = $ovenDir.'/'.$systemName.'Queries.inc';
184 
185  $contents = '<?'."php\n\n";
186  $contents .= "require_once SQ_LIB_PATH.'/MatrixDAL/MatrixDAL.inc';\n";
187  $contents .= "class $systemName"."Queries\n{\n\n";
188  // Determine what XML files are present in the oven directory for this
189  // system, and print out the method for each.
190  $types = array('.xml');
191  $xmlFiles = FileSystem::listDirectory($ovenDir, $types, TRUE, FALSE);
192  foreach ($xmlFiles as $queryName) {
193  $queryName = basename($queryName, '.xml');
194  if (substr($queryName, -3) !== '.rb') {
195  $contents .= self::bakeQueryMethod($systemName, $queryName);
196  }
197  }
198 
199  $contents .= "\n\n}\n?>";
200 
201  file_put_contents($fileName, $contents);
202 
203  }//end bakeQueriesFile()
204 
205 
221  public static function bakeQueryMethod($systemName, $queryName)
222  {
223  // Output the method signature.
224  $methodName = 'prepare'.ucwords($queryName).'Query';
225  $contents = "public static function $methodName(array \$data, \$bind)\n{\n";
226  // Print out the initialisation of hard coded values.
227  $contents .= self::_printPlaceHolderVariables($systemName, $queryName);
228  // Get the values required for our assertions.
229  $contents .= "try {\n";
230  // Loop through each assertion, and output a call that assigns its
231  // value to a variable.
232  $assertions = self::_getQueryAssertions($systemName, $queryName);
233  // Print out the code for including the assertions systems and
234  // initialising their values.
235  $contents .= self::_printAssertionCalls($assertions);
236  $contents .= "} catch (ChannelException \$e) {\n";
237  $contents .= " // Exception thrown in the called channel.\n";
238  $contents .= "}\n";
239 
240  // Get all the conditions that represent all of the query's
241  // combinations, and add it to the contents.
242  if (self::_queryRequiresRollback($systemName, $queryName)) {
243  $contents .= 'if (SQ_ROLLBACK_VIEW) {'."\n";
244  $queryBody = self::mergeQuery($systemName, $queryName.'.rb');
245  $allBindVars = self::findRollbackBindVars($queryBody);
246  $contents .= $queryBody;
247  if (!empty($allBindVars)) {
248  foreach ($allBindVars as $bindVar) {
249  $contents .= '$query->bind(\''.$bindVar.'\', $_SESSION[\'sq_rollback_view\'][\'rollback_time\']);'."\n";
250  }//end foreach
251  } else {
252  $contents .= '$query->bind(\':rbtime\', $_SESSION[\'sq_rollback_view\'][\'rollback_time\']);'."\n";
253  }//end if
254  $contents .= '} else {'."\n";
255  $queryBody = self::mergeQuery($systemName, $queryName);
256  $contents .= $queryBody;
257  $contents .= '}';
258  } else {
259  $queryBody = self::mergeQuery($systemName, $queryName);
260  $contents .= $queryBody;
261  }
262  $subQueries = self::printSubQueries($systemName, $queryName);
263  $contents .= $subQueries;
264  $contents .= self::_printKeywords($systemName, $queryName);
265  $contents .= self::_printBindings($systemName, $queryName);
266  $contents .= "\n}//end $methodName()\n";
267 
268  return $contents;
269 
270  }//end bakeQueryMethod()
271 
272 
280  public static function findRollbackBindVars($queryBody)
281  {
282  $sqlQuery = preg_match_all("/\('(.*)'\)/", $queryBody, $matches);
283 
284  $allBindVars = Array();
285  if (isset($matches[1]) && !empty($matches[1])) {
286  foreach ($matches[1] as $match) {
287  $query = DAL::getQueryObject($match);
288  $rollbackBinds = preg_match_all('/(:rbtime[0-9]*)\b/i', $query->getSql(), $foundBindVars);
289  if (isset($foundBindVars[1]) && !empty($foundBindVars[1])) {
290  $allBindVars = array_merge($allBindVars, $foundBindVars[1]);
291  }//end if
292  }//end foreach
293  return $allBindVars;
294  }//end if
295 
296 
297  return Array();
298 
299  }//end findRollbackBindVars()
300 
301 
321  protected static function _printBindings($systemName, $queryName, $printBindings=TRUE)
322  {
323  $doc = self::getQueryXML($systemName, $queryName);
324  $contents = '';
325 
326  $bindingList = $doc->getElementsByTagName('binding');
327  // Create an array containing our place holder names, so we can easily
328  // find if a variable name has been generated by the system.
329  $placeHolders = array();
330  $placeHoldersList = $doc->getElementsByTagName('placeholder');
331  foreach ($placeHoldersList as $element) {
332  $varName = $element->getAttribute('var_name');
333  $varValue = $element->getAttribute('value');
334  $placeHolders[$varName] = $varValue;
335  }
336 
337  $usedNames = array();
338  $lobCount = 0;
339 
340  foreach ($bindingList as $binding) {
341  $requiresLob = FALSE;
342  $bindName = $binding->getAttribute('name');
343  // We need to determine what data type the column is.
344  $columnType = $binding->getAttribute('column_type');
345  if ($columnType !== '') {
346  //$dataType = self::_getPdoDataType($system, $table, $column, $columnType);
347  } else {
348  $column = $binding->getAttribute('column');
349  $table = $binding->getAttribute('table');
350  $system = $binding->getAttribute('system');
351  //$dataType = self::_getPdoDataType($system, $table, $column);
352 
353  if (empty($table) === TRUE) {
354  // We are not tied to any column. Therefore we have no idea whether
355  // it requires a LOB or not. I think it's fair to assume that if
356  // it's a placeholder, then it will not need a LOB.
357  if (array_key_exists($bindName, $placeHolders) === FALSE) {
358  $requiresLob = NULL;
359  }
360  } else if (self::_fieldRequiresOciClob($table, $column) === TRUE) {
361  // OCI;
362  $requiresLob = TRUE;
363  }
364  }
365 
366  $varName = '';
367  $index = substr($bindName, 1);
368  // Check if the binding is a hard coded value. If it is, set its'
369  // variable to be the same as defined in _printPlaceHoldersVariables
370  // otherwise print an index into the $data array.
371  if (isset($placeHolders[$bindName]) === TRUE) {
372  // This is a place holder for a hard-coded value.
373  $varName = '$'.substr($bindName, 1);
374  } else {
375  // This is not hard-coded, so should be referenced in the $data
376  // array.
377  $varName = "\$data['$index']";
378  }
379 
380  if ($printBindings === TRUE) {
381  // Add check for array values.
382  $bindCall = '';
383  if (isset($placeHolders[$bindName]) === FALSE) {
384  $bindCall = "\nif (array_key_exists('$index',\$data) === TRUE) {\n";
385  }
386 
387  if (MatrixDAL::getDbType() === 'oci') {
388  if ($requiresLob === TRUE) {
389  $lobCount++;
390  $bindCall .= "\$lob{$lobCount} = oci_new_descriptor(MatrixDAL::getDb(), OCI_D_LOB);\n";
391  $bindCall .= "\$query->bind('$bindName', \$lob{$lobCount}, PDO::PARAM_LOB);\n";
392  $bindCall .= "\$lob{$lobCount}->writeTemporary($varName, OCI_TEMP_CLOB);\n";
393  } else if ($requiresLob === NULL) {
394  // Not tied to a column. If we got passed a LOB, then use it,
395  // otherwise, pass it as normal. Note that we cannot use instanceof()
396  // because of the dodgy class name OCI8 extension uses.
397  $bindCall .= "if (is_object($varName) && get_class($varName) === 'OCI-Lob') {\n";
398  $bindCall .= "\$query->bind('$bindName', $varName, PDO::PARAM_LOB);\n";
399  $bindCall .= "} else {\n";
400  $bindCall .= "\$query->bind('$bindName', $varName);\n";
401  $bindCall .= "}\n";
402  } else {
403  $bindCall .= "\$query->bind('$bindName', $varName);\n";
404  }
405  } else {
406  //$bindCall .= "\$query->bind('$bindName', $varName, $dataType);\n";
407  $bindCall .= "\$query->bind('$bindName', $varName);\n";
408  }
409 
410  if (isset($placeHolders[$bindName]) === FALSE) {
411  $bindCall .= "}\n\n";
412  }
413 
414  // Add this binding to the contents.
415  $contents .= $bindCall;
416  }
417 
418  }//end foreach
419 
420  $contents .= "return \$query;\n";
421 
422  return $contents;
423 
424  }//end _printBindings()
425 
426 
453  protected static function _queryRequiresRollback($systemName, $queryName)
454  {
455  $filePath = realpath(SQ_SYSTEM_ROOT).'/core/lib/DAL/Oven/'.$systemName.'/'.$queryName.'.xml';
456  $doc = new DOMDocument();
457  $doc->load($filePath);
458  $xpath = new DOMXPath($doc);
459  $nodes = $xpath->query('//query/primary/select/from/table');
460  foreach ($nodes as $node) {
461  $table = $node->nodeValue;
462  if (self::_tableRequiresRollback($table)) {
463  return TRUE;
464  }
465  }
466 
467  return FALSE;
468 
469  }//end _queryRequiresRollback()
470 
471 
485  protected static function _tableRequiresRollback($table)
486  {
487  // lop off 'sq_' prefix if it exists
488  if (substr($table, 0, 3) == 'sq_') {
489  $table = substr($table, 3);
490  }
491 
492  // This might be called before anything actually is installed, so if
493  // the core queries haven't yet been installed, assume that there are
494  // no packages installed.
495  // TODO:
496  $ovenDir = SQ_LIB_PATH.'/DAL/Oven/core/coreQueries.xml';
497 
498  try {
499  $packages = $GLOBALS['SQ_SYSTEM']->getInstalledPackages();
500  } catch (Exception $e) {
501  $packages = Array();
502  }
503 
504  // If no packages have been installed yet, we want to at least include
505  // the core package.
506  //
507  // This is a multi-dimensional array because that's what getInstalledPackages
508  // returns later. We only care about including the code_name when we install.
509 
510  if (empty($packages)) {
511  $packages[] = array(
512  'code_name' => '__core__',
513  );
514  }
515 
516  // Loop through each tables.xml file, see if we can find a table entry
517  // for this table that requires rollback.
518  foreach ($packages as $_key => $package_details) {
519  $package_name = $package_details['code_name'];
520 
521  if ($package_name === '__core__') {
522  $dir = SQ_CORE_PACKAGE_PATH;
523  } else {
524  $dir = SQ_PACKAGES_PATH.'/'.$package_name;
525  }
526  $filename = $dir.'/tables.xml';
527 
528  if (file_exists($filename) === TRUE) {
529  $doc = new DOMDocument('1.0', 'iso-8859-1');
530  $doc->load($filename);
531 
532  $xpath = new DOMXPath($doc);
533  $count = $xpath->evaluate('count(/schema/tables/table[@name="'.$table.'" and @require_rollback="1"])');
534  if ($count > 0) return TRUE;
535  }
536  }
537 
538  // could not find a table, so return FALSE
539  return FALSE;
540 
541  }//end _tableRequiresRollback()
542 
543 
561  protected static function _fieldRequiresOciClob($table, $field)
562  {
563  // OCI:
564  if ((empty($table) === TRUE) || (empty($field) === TRUE)) {
565  // This bind variable was not completely inside a "column" attribute
566  // (eg. it might be inside a function). Assume it's not a LOB for now
567  return FALSE;
568  }
569 
570  // lop off 'sq_' prefix if it exists
571  if (substr($table, 0, 3) == 'sq_') {
572  $table = substr($table, 3);
573  }
574 
575  // Is this a view? Assume innocent until proven guilty
576  if (substr($table, 0, 3) == 'vw_') {
577  return FALSE;
578  }
579 
580  // lop off 'rb_' prefix if it exists. If this is a rollback table, then
581  // the type of column will be the same as that of the main table
582  if (substr($table, 0, 3) == 'rb_') {
583  $table = substr($table, 3);
584  }
585 
586  // This might be called before anything actually is installed, so if
587  // the core queries haven't yet been installed, assume that there are
588  // no packages installed.
589  // TODO:
590  $ovenDir = SQ_LIB_PATH.'/DAL/Oven/core/coreQueries.xml';
591 
592  try {
593  $packages = $GLOBALS['SQ_SYSTEM']->getInstalledPackages();
594  } catch (Exception $e) {
595  $packages = Array();
596  }
597 
598  // If no packages have been installed yet, we want to at least include
599  // the core package.
600  if (empty($packages)) {
601  $packages[] = Array('code_name' => '__core__');
602  }
603 
604  // Loop through each tables.xml file, see if we can find a table entry
605  // for this table that requires rollback.
606  foreach ($packages as $package_name) {
607  $package_name = $package_name['code_name'];
608  if ($package_name === '__core__') {
609  $dir = SQ_CORE_PACKAGE_PATH;
610  } else {
611  $dir = SQ_PACKAGES_PATH.'/'.$package_name;
612  }
613  $filename = $dir.'/tables.xml';
614 
615  if (file_exists($filename) === TRUE) {
616  $doc = new DOMDocument('1.0', 'iso-8859-1');
617  $doc->load($filename);
618 
619  // We are going to take advantage of the fact that a [C]LOB
620  // field in Oracle is always going to be specified as a "type
621  // variation" in Matrix's "tables.xml" file. PostgreSQL types
622  // generally come first, and "TEXT" was also used for MySQL
623  // when Matrix supported that, so Oracle's type is demoted to
624  // a type variation.
625 
626  // First find out if such a field actually exists...
627  $xpath = new DOMXPath($doc);
628  $count = $xpath->evaluate('count(/schema/tables/table[@name="'.$table.'"]/columns/column[@name="'.$field.'"])');
629  if ($count > 0) {
630  // we found it, let's see if it has a OCI variation, and
631  // that it's of the correct type
632  $nodes = $xpath->query('/schema/tables/table[@name="'.$table.'"]/columns/column[@name="'.$field.'"]/type_variations/oci');
633  foreach ($nodes as $node) {
634  if (strtoupper($node->nodeValue) == 'CLOB') {
635  return TRUE;
636  }
637  }
638 
639  // we couldn't find anything
640  return FALSE;
641  }
642  }
643  }
644 
645  // could not find the field! Throw an exception.
646  return FALSE;
647  //throw new Exception('Could not determine if '.$table.'.'.$field.' requires a LOB. The field does not exist in the table definition.');
648 
649  }//end _tableRequiresRollback()
650 
651 
662  public static function processQueriesFile($systemName, $fileName)
663  {
664  if (file_exists($fileName) === FALSE) {
665  return FALSE;
666  }
667 
668  $doc = new DomDocument();
669  $doc->load($fileName);
670  $queryNode = $doc->getElementsByTagName('queries')->item(0);
671 
672  $currentDbType = DAL::getDbType();
673 
674  foreach ($queryNode->childNodes as $child) {
675  if ($child->nodeType !== 1) {
676  // Text Node. Probably just whitespace or a comment. We don't
677  // want to deal with it.
678  continue;
679  }
680 
681  if ($child->tagName === 'query') {
682  if ($child->hasAttribute('id') === FALSE) {
683  $error = 'ID not specified for query in '.$systemName;
684  throw new DALBakerException($error);
685  }
686 
687  if ($child->hasAttribute('databases') === TRUE) {
688  $dbString = $child->getAttribute('databases');
689  $dbString = str_replace(' ', '', $dbString);
690  $dbs = explode(',', $dbString);
691  if (in_array($currentDbType, $dbs) === FALSE) {
692  $id = $child->getAttribute('id');
693  echo 'Skipped Query: '.$id." (not for this DB type)\n";
694  continue;
695  }
696  }
697 
698  if ($child->hasAttribute('hookid') === TRUE) {
699  $hookId = $child->getAttribute('hookid');
700  if (substr_count($hookId, '.') === 1) {
701  // We have a fragment.
702  self::addQueryFragment($systemName, $child);
703  } else {
704  // We have a sub query with a three part hookid (*.*.*).
705  self::addSubQuery($systemName, $child);
706  }
707  } else {
708  // New Query.
709  $id = $child->getAttribute('id');
710  self::addQuery($systemName, $child);
711  if (self::_queryRequiresRollback($systemName, $id)) {
712  $rollback_child = self::_createRollbackXML($systemName, $child);
713  $rollback_child->setAttribute('id', $id.'.rb');
714  self::addQuery($systemName, $rollback_child);
715  }
716  }
717  }//end if
718  }//end foreach childnodes
719 
720  }//end processQueriesFile()
721 
722 
733  protected static function _createRollbackXML($systemName, DOMNode $queryNode)
734  {
735  $doc = new DOMDocument();
736  $queryNode = $doc->importNode($queryNode, TRUE);
737  $doc->appendChild($queryNode);
738  $xpath = new DOMXPath($doc);
739 
740  $whereNodes = $xpath->query('//query//select/where');
741  if (count($whereNodes) == 0)
742  {
743  $whereNode = NULL;
744  } else {
745  $whereNode = $whereNodes->item(0);
746  }
747 
748  $nodes = $xpath->query('//query//select/from/table');
749  foreach ($nodes as $node) {
750  $table = $node->nodeValue;
751 
752  if (self::_tableRequiresRollback($table)) {
753  // TODO: replace "from" tables with 'sq_rb_'
754 
755  // Add rollback where clause
756  $fieldNodes = $xpath->query('//query//select/fields/field');
757  foreach ($fieldNodes as $fieldNode) {
758  $table = $fieldNode->getAttribute('table');
759  if (self::_tableRequiresRollback($table)) {
760  $rbTableName = substr_replace($table, 'sq_rb_', 0, 3);
761  $fieldNode->setAttribute('table', $rbTableName);
762  }
763  }
764 
765  // Replace where attributes
766  $condNodes = $xpath->query('//query//select/where//*');
767  foreach ($condNodes as $condNode) {
768  $table = $condNode->getAttribute('table');
769  if (self::_tableRequiresRollback($table)) {
770  $rbTableName = substr_replace($table, 'sq_rb_', 0, 3);
771  $condNode->setAttribute('table', $rbTableName);
772  }
773  }
774 
775  // Replace order-by attributes
776  $orderByNodes = $xpath->query('//query//select/order-by//*');
777  foreach ($orderByNodes as $orderByNode) {
778  $table = $orderByNode->getAttribute('table');
779  if (self::_tableRequiresRollback($table)) {
780  $rbTableName = substr_replace($table, 'sq_rb_', 0, 3);
781  $orderByNode->setAttribute('table', $rbTableName);
782  }
783  }
784 
785  // Replace group-by attributes
786  $groupByNodes = $xpath->query('//query//select/group-by//*');
787  foreach ($groupByNodes as $groupByNode) {
788  $table = $groupByNode->getAttribute('table');
789  if (self::_tableRequiresRollback($table)) {
790  $rbTableName = substr_replace($table, 'sq_rb_', 0, 3);
791  $groupByNode->setAttribute('table', $rbTableName);
792  }
793  }
794  }
795  }
796 
797  // Add rollback where queries, no matter where they are found
798  $fromNodes = $xpath->query('//query//select/from/table');
799  $count = 0;
800  foreach ($fromNodes as $fromNode) {
801  $table = trim($fromNode->nodeValue);
802  $rollbackTable = substr_replace($table, 'sq_rb_', 0, 3);
803 
804  if (self::_tableRequiresRollback($table)) {
805  $fromNode->nodeValue = substr_replace($table, 'sq_rb_', 0, 3);
806 
807  // Go back to the table's select node, find its where node
808  $selectNode = $fromNode->parentNode->parentNode;
809  $whereNode = NULL;
810  foreach ($selectNode->childNodes as $childNode) {
811  if ($childNode->nodeName == 'where') {
812  $whereNode = $childNode;
813  break;
814  }
815  }
816 
817  // If no where node exists, create it
818  if (is_null($whereNode)) {
819  $whereNode = $doc->createElement('where');
820  $selectNode->appendChild($whereNode);
821  }
822 
823  $lessNode = $doc->createElement('equal-or-less', ':rbtime'.$count);
824  $count++;
825  $lessNode->setAttribute('table', $rollbackTable);
826  $lessNode->setAttribute('column', 'sq_eff_from');
827  $whereNode->appendChild($lessNode);
828 
829  $orNode = $doc->createElement('or');
830  $whereNode->appendChild($orNode);
831 
832  $greaterNode = $doc->createElement('equal-or-greater', ':rbtime'.$count);
833  $count++;
834  $greaterNode->setAttribute('table', $rollbackTable);
835  $greaterNode->setAttribute('column', 'sq_eff_to');
836  $orNode->appendChild($greaterNode);
837 
838  $nullNode = $doc->createElement('is-null');
839  $nullFieldNode = $doc->createElement('field');
840  $nullFieldNode->setAttribute('table', $rollbackTable);
841  $nullFieldNode->setAttribute('column', 'sq_eff_to');
842  $nullNode->appendChild($nullFieldNode);
843  $orNode->appendChild($nullNode);
844  }
845  }
846 
847  return $queryNode;
848 
849  }//end _createRollbackXML()
850 
851 
852 }
853 ?>