TeraWurflDatabase
[ class tree: TeraWurflDatabase ] [ index: TeraWurflDatabase ] [ all elements ]

Source for file TeraWurflDatabase_MySQL4.php

Documentation is available at TeraWurflDatabase_MySQL4.php

  1. <?php
  2. /**
  3.  * Tera_WURFL - PHP MySQL driven WURFL
  4.  * 
  5.  * Tera-WURFL was written by Steve Kamerman, and is based on the
  6.  * Java WURFL Evolution package by Luca Passani and WURFL PHP Tools by Andrea Trassati.
  7.  * This version uses a MySQL database to store the entire WURFL file, multiple patch
  8.  * files, and a persistent caching mechanism to provide extreme performance increases.
  9.  * 
  10.  * @package TeraWurflDatabase
  11.  * @author Steve Kamerman <stevekamerman AT gmail.com>
  12.  * @version Stable 2.1.3 $Date: 2010/09/18 15:43:21
  13.  * @license http://www.mozilla.org/MPL/ MPL Vesion 1.1
  14.  */
  15. /**
  16.  * Provides connectivity from Tera-WURFL to MySQL 4.  This connector is also compatible with MySQL 5.
  17.  * @package TeraWurflDatabase
  18.  */
  19.  
  20.     
  21.     // Properties
  22.     public $errors;
  23.     public $db_implements_ris = false;
  24.     public $db_implements_ld = false;
  25.     public $numQueries = 0;
  26.     public $connected = false;
  27.     
  28.     protected $dbcon;
  29.     
  30.     public $maxquerysize = 0;
  31.     /**
  32.      * The maximum number of new rows that the database can handle in one INSERT statement
  33.      * @var unknown_type 
  34.      */
  35.     protected static $DB_MAX_INSERTS 500;
  36.     
  37.     public function __construct(){
  38.         parent::__construct();
  39.     }
  40.     /**
  41.      * Destructor, disconnect from database
  42.      */
  43.     public function __destruct(){
  44.         @$this->dbcon->close();
  45.     }
  46.     // Device Table Functions (device,hybrid,patch)
  47.     public function getDeviceFromID($wurflID){
  48.         $this->numQueries++;
  49.         $res $this->dbcon->query("SELECT * FROM `".TeraWurflConfig::$TABLE_PREFIX.'Merge'."` WHERE `deviceID`=".$this->SQLPrep($wurflID)) or die($this->dbcon->error);
  50.         if($res->num_rows == 0){
  51.             $res->close();
  52.             throw new Exception("Tried to lookup an invalid WURFL Device ID: $wurflID");
  53.         }
  54.         $data $res->fetch_assoc();
  55.         $res->close();
  56.         return unserialize($data['capabilities']);
  57.     }
  58.     public function getActualDeviceAncestor($wurflID){
  59.         if($wurflID == "" || $wurflID == WurflConstants::$GENERIC)
  60.             return WurflConstants::$GENERIC;
  61.         $device $this->getDeviceFromID($wurflID);
  62.         if($device['actual_device_root']){
  63.             return $device['id'];
  64.         }else{
  65.             return $this->getActualDeviceAncestor($device['fall_back']);
  66.         }
  67.     }
  68.     public function getFullDeviceList($tablename){
  69.         $this->numQueries++;
  70.         $res $this->dbcon->query("SELECT `deviceID`, `user_agent` FROM `$tablename` WHERE `match`=1");
  71.         if($res->num_rows == 0){
  72.             $res->close();
  73.             return array();
  74.         }
  75.         $data array();
  76.         while($row $res->fetch_assoc()){
  77.             $data[$row['deviceID']]=$row['user_agent'];
  78.         }
  79.         return $data;
  80.     }
  81.     // Exact Match
  82.     public function getDeviceFromUA($userAgent){
  83.         $this->numQueries++;
  84.         $query "SELECT `deviceID` FROM `".TeraWurflConfig::$TABLE_PREFIX.'Merge'."` WHERE `user_agent`=".$this->SQLPrep($userAgent);
  85.         $res $this->dbcon->query($query);
  86.         if($res->num_rows == 0){
  87.             $res->close();
  88.             return false;
  89.         }
  90.         $data $res->fetch_assoc();
  91.         $res->close();
  92.         return $data['deviceID'];
  93.     }
  94.     public function loadDevices(&$tables){
  95.         $insert_errors array();
  96.         $insertcache array();
  97.         $insertedrows 0;
  98.         $this->createIndexTable();
  99.         $this->createSettingsTable();
  100.         $this->clearMatcherTables();
  101.         foreach($tables as $table => $devices){
  102.             // insert records into a new temp table until we know everything is OK
  103.             $temptable $table (self::$DB_TEMP_EXT);
  104.             $parts explode('_',$table);
  105.             $matcher array_pop($parts);
  106.             $this->createGenericDeviceTable($temptable);
  107.             foreach($devices as $device){
  108.                 $this->dbcon->query("INSERT INTO `".TeraWurflConfig::$TABLE_PREFIX.'Index'."` (`deviceID`,`matcher`) VALUE (".$this->SQLPrep($device['id']).",".$this->SQLPrep($matcher).")");
  109.                 // convert device root to tinyint format (0|1) for db
  110.                 if(strlen($device['user_agent']255){
  111.                     $insert_errors["Warning: user agent too long: \"".($device['id']).'"';
  112.                 }
  113.                 $insertcache[sprintf("(%s,%s,%s,%s,%s,%s)",
  114.                     $this->SQLPrep($device['id']),
  115.                     $this->SQLPrep($device['user_agent']),
  116.                     $this->SQLPrep($device['fall_back']),
  117.                     $this->SQLPrep((isset($device['actual_device_root']))?$device['actual_device_root']:''),
  118.                     preg_match('/^DO_NOT_MATCH/',$device['user_agent'])'0''1',
  119.                     $this->SQLPrep(serialize($device))
  120.                 );
  121.                 // This batch of records is ready to be inserted
  122.                 if(count($insertcache>= self::$DB_MAX_INSERTS){
  123.                     $query "INSERT INTO `$temptable` (`deviceID`, `user_agent`, `fall_back`, `actual_device_root`, `match`, `capabilities`) VALUES ".implode(",",$insertcache);
  124.                     $this->dbcon->query($queryor $insert_errors["DB server reported error on id \"".$device['id']."\": ".$this->dbcon->error;
  125.                     $insertedrows += $this->dbcon->affected_rows;
  126.                     $insertcache array();
  127.                     $this->numQueries++;
  128.                     $this->maxquerysize = (strlen($query)>$this->maxquerysize)strlen($query)$this->maxquerysize;
  129.                 }
  130.             }
  131.             // some records are probably left in the insertcache
  132.             if(count($insertcache0){
  133.                 $query "INSERT INTO `$temptable` (`deviceID`, `user_agent`, `fall_back`, `actual_device_root`, `match`, `capabilities`) VALUES ".implode(",",$insertcache);
  134.                     $this->dbcon->query($queryor $insert_errors["DB server reported error on id \"".$device['id']."\": ".$this->dbcon->error;
  135.                     $insertedrows += $this->dbcon->affected_rows;
  136.                     $insertcache array();
  137.                     $this->numQueries++;
  138.                     $this->maxquerysize = (strlen($query)>$this->maxquerysize)strlen($query)$this->maxquerysize;
  139.             }
  140.             if(count($insert_errors0){
  141.                 // Roll back changes
  142.                 // leave the temp table in the DB for manual inspection
  143.                 $this->errors = array_merge($this->errors,$insert_errors);
  144.                 return false;
  145.             }
  146.             $this->numQueries++;
  147.             $this->dbcon->query("DROP TABLE IF EXISTS `$table`");
  148.             $this->numQueries++;
  149.             $this->dbcon->query("RENAME TABLE `$temptable` TO `$table`");
  150.         }
  151.         // Create Merge Table
  152.         $this->createMergeTable(array_keys($tables));
  153.         return true;
  154.     }
  155.     /**
  156.      * Drops and creates the given device table
  157.      *
  158.      * @param string Table name (ex: TeraWurflConfig::$HYBRID)
  159.      * @return boolean success
  160.      */
  161.     public function createGenericDeviceTable($tablename){
  162.         $droptable "DROP TABLE IF EXISTS ".$tablename;
  163.         $createtable "CREATE TABLE `".$tablename."` (
  164.             `deviceID` varchar(64) binary NOT NULL default '',
  165.             `user_agent` varchar(255) default NULL,
  166.             `fall_back` varchar(64) default NULL,
  167.             `actual_device_root` tinyint(1) default '0',
  168.             `match` tinyint(1) default '1',
  169.             `capabilities` mediumtext,
  170.             PRIMARY KEY  (`deviceID`),
  171.             KEY `fallback` (`fall_back`),
  172.             KEY `useragent` (`user_agent`),
  173.             KEY `dev_root` (`actual_device_root`),
  174.             KEY `idxmatch` (`match`)
  175.             ) ENGINE=MyISAM";
  176.         $this->numQueries++;
  177.         $this->dbcon->query($droptable);
  178.         $this->numQueries++;
  179.         $this->dbcon->query($createtable);
  180.         return true;
  181.     }
  182.     /**
  183.      * Drops then creates all the UserAgentMatcher device tables
  184.      * @return boolean success
  185.      */
  186.     protected function clearMatcherTables(){
  187.         foreach(UserAgentFactory::$matchers as $matcher){
  188.             $table TeraWurflConfig::$TABLE_PREFIX."_".$matcher;
  189.             $this->createGenericDeviceTable($table);
  190.         }
  191.         return true;
  192.     }
  193.     /**
  194.      * Drops and creates the MERGE table
  195.      *
  196.      * @param array Table names
  197.      * @return boolean success
  198.      */
  199.     public function createMergeTable($tables){
  200.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Merge';
  201.         foreach($tables as &$table){$table="SELECT * FROM `$table`";}
  202.         $droptable "DROP TABLE IF EXISTS ".$tablename;
  203.         $this->createGenericDeviceTable($tablename);
  204.         $createtable "INSERT INTO `$tablename".implode(" UNION ALL ",$tables);
  205.         $this->numQueries++;
  206.         $this->dbcon->query($createtableor die("ERROR: ".$this->dbcon->error);
  207.         return true;
  208.     }
  209.     /**
  210.      * Drops and creates the index table
  211.      *
  212.      * @return boolean success
  213.      */
  214.     public function createIndexTable(){
  215.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Index';
  216.         $droptable "DROP TABLE IF EXISTS ".$tablename;
  217.         $createtable "CREATE TABLE `".$tablename."` (
  218.   `deviceID` varchar(64) binary NOT NULL default '',
  219.   `matcher` varchar(64) NOT NULL,
  220.   PRIMARY KEY  (`deviceID`)
  221. ) ENGINE=MyISAM";
  222.         $this->numQueries++;
  223.         $this->dbcon->query($droptable);
  224.         $this->numQueries++;
  225.         $this->dbcon->query($createtable);
  226.         return true;
  227.     }
  228.     /**
  229.      * Creates the settings table if it does not already exist
  230.      * @return boolean success
  231.      */
  232.     public function createSettingsTable(){
  233.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Settings';
  234.         $checktable "SHOW TABLES LIKE '$tablename'";
  235.         $this->numQueries++;
  236.         $res $this->dbcon->query($checktable);
  237.         if($res->num_rows 0return true;
  238.         $createtable "CREATE TABLE `".$tablename."` (
  239.   `id` varchar(64) NOT NULL,
  240.   `value` varchar(255) DEFAULT NULL,
  241.   PRIMARY KEY (`id`)
  242. ) ENGINE=MyISAM";
  243.         $this->numQueries++;
  244.         $this->dbcon->query($createtable);
  245.         return true;
  246.     }
  247.     // Cache Table Functions
  248.     
  249.     // should return (bool)false or the device array
  250.     public function getDeviceFromCache($userAgent){
  251.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Cache';
  252.         $this->numQueries++;
  253.         $res $this->dbcon->query("SELECT * FROM `$tablename` WHERE `user_agent`=".$this->SQLPrep($userAgent)) or die("Error: ".$this->dbcon->error);
  254.         if($res->num_rows == 0){
  255.             $res->close();
  256.             //echo "[[UA NOT FOUND IN CACHE: $userAgent]]";
  257.             return false;
  258.         }
  259.         $data $res->fetch_assoc();
  260.         $res->close();
  261.         return unserialize($data['cache_data']);
  262.         
  263.     }
  264.     public function saveDeviceInCache($userAgent,$device){
  265.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Cache';
  266.         $ua $this->SQLPrep($userAgent);
  267.         $packed_device $this->SQLPrep(serialize($device));
  268.         $this->numQueries++;
  269.         $this->dbcon->query("INSERT DELAYED INTO `$tablename` (`user_agent`,`cache_data`) VALUES ($ua,$packed_device)");
  270.         if($this->dbcon->affected_rows 0){
  271.             return true;
  272.         }
  273.         return false;
  274.     }
  275.     public function createCacheTable(){
  276.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Cache';
  277.         $droptable "DROP TABLE IF EXISTS `$tablename`";
  278.         $createtable "CREATE TABLE `$tablename` (
  279.             `user_agent` varchar(255) binary NOT NULL default '',
  280.             `cache_data` mediumtext NOT NULL,
  281.             PRIMARY KEY  (`user_agent`)
  282.         ) ENGINE=MyISAM";
  283.         $this->numQueries++;
  284.         $this->dbcon->query($droptable);
  285.         $this->numQueries++;
  286.         $this->dbcon->query($createtable);
  287.         return true;
  288.     }
  289.     public function createTempCacheTable(){
  290.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Cache'.self::$DB_TEMP_EXT;
  291.         $droptable "DROP TABLE IF EXISTS `$tablename`";
  292.         $createtable "CREATE TABLE `$tablename` (
  293.             `user_agent` varchar(255) binary NOT NULL default '',
  294.             `cache_data` mediumtext NOT NULL,
  295.             PRIMARY KEY  (`user_agent`)
  296.         ) ENGINE=MyISAM";
  297.         $this->numQueries++;
  298.         $this->dbcon->query($droptable);
  299.         $this->numQueries++;
  300.         $this->dbcon->query($createtable);
  301.         return true;
  302.     }
  303.     public function rebuildCacheTable(){
  304.         // We'll use this instance to rebuild the cache and to facilitate logging
  305.         $rebuilder new TeraWurfl();
  306.         $cachetable TeraWurflConfig::$TABLE_PREFIX.'Cache';
  307.         $temptable TeraWurflConfig::$TABLE_PREFIX.'Cache'.self::$DB_TEMP_EXT;
  308.         $checkcachequery "SHOW TABLES LIKE '$cachetable'";
  309.         $checkres $this->dbcon->query($checkcachequery);
  310.         $this->numQueries++;
  311.         if($checkres->num_rows === 0){
  312.             // This can only happen if the table doesn't exist
  313.             $this->createCacheTable();
  314.             $this->numQueries++;
  315.             // This table must be empty, so we're finished
  316. //            $rebuilder->toLog($query,LOG_ERR,"rebuildCacheTable");
  317.             $rebuilder->toLog("Created empty cache table",LOG_NOTICE,"rebuildCacheTable");
  318.             return true;
  319.         }
  320.         $droptemptable "DROP TABLE IF EXISTS `$temptable`";
  321.         $this->numQueries++;
  322.         $this->dbcon->query($droptemptable);
  323.         $query "RENAME TABLE `$cachetable` TO `$temptable`";
  324.         $this->numQueries++;
  325.         $this->dbcon->query($query);
  326.         $this->createCacheTable();
  327.         $query "SELECT `user_agent` FROM `$temptable`";
  328.         $this->numQueries++;
  329.         $res $this->dbcon->query($query);
  330.         if($res->num_rows == 0){
  331.             // No records in cache table == nothing to rebuild
  332.             $rebuilder->toLog("Rebuilt cache table, existing table was empty - this is very unusual.",LOG_WARNING,"rebuildCacheTable");
  333.             return true;
  334.         }
  335.         while($dev $res->fetch_assoc()){
  336.             // Just looking the device up will force it to be cached
  337.             $rebuilder->GetDeviceCapabilitiesFromAgent($dev['user_agent']);
  338.             // Reset the number of queries since we're not going to re-instantiate the object
  339.             $this->numQueries += $rebuilder->db->numQueries;
  340.             $rebuilder->db->numQueries 0;
  341.         }
  342.         $droptable "DROP TABLE IF EXISTS `$temptable`";
  343.         $this->numQueries++;
  344.         $this->dbcon->query($droptable);
  345.         $rebuilder->toLog("Rebuilt cache table.",LOG_NOTICE,"rebuildCacheTable");
  346.         return true;
  347.     }
  348.     // Supporting DB Functions
  349.     
  350.     // truncate or drop+create given table
  351.     public function clearTable($tablename){
  352.         if($tablename == TeraWurflConfig::$TABLE_PREFIX.'Cache'){
  353.             $this->createCacheTable();
  354.         }else{
  355.             $this->createGenericDeviceTable($tablename);
  356.         }
  357.     }
  358.     /**
  359.      * Establishes connection to database (does not check for DB sanity)
  360.      */
  361.     public function connect(){
  362.         $this->numQueries++;
  363.         if(strpos(TeraWurflConfig::$DB_HOST,':')){
  364.             list($host,$portexplode(':',TeraWurflConfig::$DB_HOST,2);
  365.             $this->dbcon = @new mysqli($host,TeraWurflConfig::$DB_USER,TeraWurflConfig::$DB_PASS,TeraWurflConfig::$DB_SCHEMA,$port);
  366.         }else{
  367.             $this->dbcon = @new mysqli(TeraWurflConfig::$DB_HOST,TeraWurflConfig::$DB_USER,TeraWurflConfig::$DB_PASS,TeraWurflConfig::$DB_SCHEMA);
  368.         }
  369.         if(mysqli_connect_errno()){
  370.             $this->errors[]=mysqli_connect_error();
  371.             $this->connected = mysqli_connect_errno();
  372.             return false;
  373.         }
  374.         $this->connected = true;
  375.         return true;
  376.     }
  377.     public function updateSetting($key,$value){
  378.         $tablename TeraWurflConfig::$TABLE_PREFIX.'Settings';
  379.         $query sprintf("REPLACE INTO `%s` (`%s`, `%s`) VALUES (%s, %s)"$tablename'id''value'$this->SQLPrep($key)$this->SQLPrep($value));
  380.         $this->numQueries++;
  381.         $this->dbcon->query($query);
  382.     }
  383.     public function getSetting($key){
  384.         $query "SELECT `value` FROM `".TeraWurflConfig::$TABLE_PREFIX.'Settings'."` WHERE `id` = ".$this->SQLPrep($key);
  385.         $this->numQueries++;
  386.         $res $this->dbcon->query($query);
  387.         if($res->num_rows == 0return null;
  388.         $row $res->fetch_assoc();
  389.         return $row['value'];
  390.     }
  391.  
  392.     // prep raw text for use in queries (adding quotes if necessary)
  393.     public function SQLPrep($value){
  394.         if($value == ''$value 'NULL';
  395.         else if (!is_numeric($value|| $value[0== '0'$value "'" $this->dbcon->real_escape_string($value"'"//Quote if not integer
  396.         return $value;
  397.     }
  398.     public function getTableList(){
  399.         $tablesres $this->dbcon->query("SHOW TABLES");
  400.         $tables array();
  401.         while($table $tablesres->fetch_row())$tables[]=$table[0];
  402.         $tablesres->close();
  403.         return $tables;
  404.     }
  405.     public function getMatcherTableList(){
  406.         $tablesres $this->dbcon->query("SHOW TABLES LIKE '".TeraWurflConfig::$TABLE_PREFIX."\\_%'");
  407.         $tables array();
  408.         while($table $tablesres->fetch_row())$tables[]=$table[0];
  409.         $tablesres->close();
  410.         return $tables;
  411.     }
  412.     public function getTableStats($table){
  413.         $stats array();
  414.         $fields array();
  415.         $fieldnames array();
  416.         $fieldsres $this->dbcon->query("SHOW COLUMNS FROM ".$table);
  417.         while($row $fieldsres->fetch_assoc()){
  418.             $fields['CHAR_LENGTH(`'.$row['Field'].'`)';
  419.             $fieldnames[]=$row['Field'];
  420.         }
  421.         $fieldsres->close();
  422.         $bytesizequery "SUM(".implode('+',$fields).") AS `bytesize`";
  423.         $query "SELECT COUNT(*) AS `rowcount`, $bytesizequery FROM `$table`";
  424.         $res $this->dbcon->query($query);
  425.         $rows $res->fetch_assoc();
  426.         $stats['rows'$rows['rowcount'];
  427.         $stats['bytesize'$rows['bytesize'];
  428.         $res->close();
  429.         if(in_array("actual_device_root",$fieldnames)){
  430.             $res $this->dbcon->query("SELECT COUNT(*) AS `devcount` FROM `$table` WHERE actual_device_root=1");
  431.             $row $res->fetch_assoc();
  432.             $stats['actual_devices'$row['devcount'];
  433.             $res->close();
  434.         }
  435.         return $stats;
  436.     }
  437.     public function getCachedUserAgents(){
  438.         $uas array();
  439.         $cacheres $this->dbcon->query("SELECT user_agent FROM ".TeraWurflConfig::$TABLE_PREFIX.'Cache'." ORDER BY user_agent");
  440.         while($ua $cacheres->fetch_row())$uas[]=$ua[0];
  441.         $cacheres->close();
  442.         return $uas;
  443.     }
  444.     public function getServerVersion(){
  445.         $res $this->dbcon->query("SELECT version() AS `version`");
  446.         if(!$res || $res->num_rows == 0return '[not connected]';
  447.         $row $res->fetch_assoc();
  448.         return($row['version']);
  449.     }
  450. }

Documentation generated on Sun, 19 Sep 2010 00:16:00 +0000 by phpDocumentor 1.4.3