Source for file TeraWurflDatabase_MSSQL2005.php
Documentation is available at TeraWurflDatabase_MSSQL2005.php
* Tera_WURFL - PHP MySQL driven WURFL
* Tera-WURFL was written by Steve Kamerman, and is based on the
* Java WURFL Evolution package by Luca Passani and WURFL PHP Tools by Andrea Trassati.
* This version uses a MySQL database to store the entire WURFL file, multiple patch
* files, and a persistent caching mechanism to provide extreme performance increases.
* @package TeraWurflDatabase
* @author Steve Kamerman <stevekamerman AT gmail.com>
* @version Stable 2.1.3 $Date: 2010/09/18 15:43:21
* @license http://www.mozilla.org/MPL/ MPL Vesion 1.1
* Provides connectivity from Tera-WURFL to Microsoft SQL Server 2005/2008
* @package TeraWurflDatabase
// If you enable this, the RIS function will be performed on the SQL server, HOWEVER,
// this is currently MUCH slower (~ x5 slower) than performing this function in PHP
* The maximum number of new rows that the database can handle in one INSERT statement
protected static $DB_MAX_INSERTS = 500;
// Device Table Functions (device,hybrid,patch)
if(!sqlsrv_has_rows($res)){
throw new Exception("Tried to lookup an invalid WURFL Device ID: $wurflID");
return WurflConstants::$GENERIC;
if($device['actual_device_root']){
$res = sqlsrv_query($this->dbcon,"SELECT deviceID, user_agent FROM $tablename");
$data[$row['deviceID']]= $row['user_agent'];
$query = "SELECT deviceID FROM ". TeraWurflConfig::$TABLE_PREFIX. 'Merge'. " WHERE user_agent=". $this->SQLPrep($userAgent);
$res = sqlsrv_query($this->dbcon,$query);
return $data['deviceID'];
// RIS == Reduction in String (reduce string one char at a time)
$result = sqlsrv_query($this->dbcon,$query);
throw new Exception(sprintf("Error in DB RIS Query: %s. \nQuery: %s\n",$this->lastDBError(),$query));
$wurflid = $data['DeviceID'];
// TODO: Implement with Stored Proc
// LD == Levesthein Distance
throw new Exception("Error: this function (LD) is not yet implemented in MySQL");die();
$safe_ua = $this->SQLPrep($userAgent);
//$res = sqlsrv_query($this->dbcon,"call TeraWurfl_LD($safe_ua,$tolerance)");
$insert_errors = array();
foreach($tables as $table => $devices){
// insert records into a new temp table until we know everything is OK
$temptable = $table . (self::$DB_TEMP_EXT);
$parts = explode('_',$table);
foreach($devices as $device){
// convert device root to tinyint format (0|1) for db
if(strlen($device['user_agent']) > 255){
$insert_errors[] = "Warning: user agent too long: \"". ($device['id']). '"';
$insertcache[] = sprintf("SELECT %s,%s,%s,%s,%s \n",
$this->SQLPrep($device['user_agent']),
$this->SQLPrep($device['fall_back']),
$this->SQLPrep((isset ($device['actual_device_root']))? $device['actual_device_root']: ''),
// This batch of records is ready to be inserted
if(count($insertcache) >= self::$DB_MAX_INSERTS){
$query = "INSERT INTO $temptable (deviceID, user_agent, fall_back, actual_device_root, capabilities) ". implode(" UNION ALL ",$insertcache);
$res = sqlsrv_query($this->dbcon,$query) or $insert_errors[] = "DB server reported error on id \"". $device['id']. "\": ". $this->lastDBError();
// some records are probably left in the insertcache
if(count($insertcache) > 0){
$query = "INSERT INTO $temptable (deviceID, user_agent, fall_back, actual_device_root, capabilities) ". implode(" UNION ALL ",$insertcache);
$res = sqlsrv_query($this->dbcon,$query) or $insert_errors[] = "DB server reported error on id \"". $device['id']. "\": ". $this->lastDBError();
if(count($insert_errors) > 0){
// leave the temp table in the DB for manual inspection
* Drops and creates the given device table
* @param string Table name (ex: TeraWurflConfig::$HYBRID)
* @return boolean success
$createtable = "CREATE TABLE [dbo].[{$tablename}](
[deviceID] [nvarchar](128) NOT NULL,
[user_agent] [nvarchar](255) NULL,
[fall_back] [nvarchar](128) NULL,
[actual_device_root] [tinyint] NULL,
[capabilities] [ntext] NULL,
CONSTRAINT [PK_{$tablename}] PRIMARY KEY CLUSTERED
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]";
$createkeys = "ALTER TABLE [dbo].[{$tablename}] ADD CONSTRAINT [DF_{$tablename}_actual_device_root] DEFAULT ((0)) FOR [actual_device_root]
CREATE NONCLUSTERED INDEX [IDX_{$tablename}_fall_back] ON [dbo].[{$tablename}] ([fall_back])
CREATE NONCLUSTERED INDEX [IDX_{$tablename}_user_agent] ON [dbo].[{$tablename}] ([user_agent])
CREATE NONCLUSTERED INDEX [IDX_{$tablename}_match] ON [dbo].[{$tablename}] ([match])";
* Drops then creates all the UserAgentMatcher device tables
* @return boolean success
$table = TeraWurflConfig::$TABLE_PREFIX. "_". $matcher;
* Drops and creates the MERGE table
* @param array Table names
* @return boolean success
foreach($tables as &$table){$table= "SELECT * FROM $table";}
$createtable = "INSERT INTO $tablename ". implode(" UNION ALL ",$tables);
* Drops and creates the index table
* @return boolean success
$createtable = "CREATE TABLE [dbo].[$tablename](
[deviceID] [nvarchar](128) NOT NULL,
[matcher] [nvarchar](64) NOT NULL,
CONSTRAINT [PK_{$tablename}] PRIMARY KEY CLUSTERED
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
sqlsrv_query($this->dbcon,$createtable);
* Creates the settings table if it does not already exist
* @return boolean success
$catalog = TeraWurflConfig::$DB_SCHEMA;
$checktable = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG = '$catalog' AND TABLE_NAME = '$tablename'";
$createtable = "CREATE TABLE [dbo].[$tablename](
[id] [nvarchar](64) NOT NULL,
[value] [nvarchar](255) NULL,
CONSTRAINT [PK_{$tablename}] PRIMARY KEY CLUSTERED
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
// should return (bool)false or the device array
$res = sqlsrv_query($this->dbcon,"SELECT * FROM $tablename WHERE user_agent=". $this->SQLPrep($userAgent)) or die("Error: ". $this->lastDBError());
//echo "[[UA NOT FOUND IN CACHE: $userAgent]]";
$packed_device = $this->SQLPrep(serialize($device));
$res = sqlsrv_query($this->dbcon,"INSERT INTO $tablename (user_agent,cache_data) VALUES ($ua,$packed_device)");
$createtable = "CREATE TABLE [dbo].[{$tablename}](
[user_agent] [nvarchar](255) NOT NULL,
[cache_data] [ntext] NOT NULL,
CONSTRAINT [PK_{$tablename}] PRIMARY KEY CLUSTERED
([user_agent] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]";
$test = sqlsrv_query($this->dbcon,$createtable);
// We'll use this instance to rebuild the cache and to facilitate logging
$temptable = TeraWurflConfig::$TABLE_PREFIX. 'Cache'. self::$DB_TEMP_EXT;
// This can only happen if the table doesn't exist
// This table must be empty, so we're finished
$rebuilder->toLog("Created empty cache table",LOG_NOTICE,"rebuildCacheTable");
$query = "SELECT user_agent FROM $temptable";
// No records in cache table == nothing to rebuild
$rebuilder->toLog("Rebuilt cache table, existing table was empty.",LOG_WARNING,"rebuildCacheTable");
// Just looking the device up will force it to be cached
$rebuilder->GetDeviceCapabilitiesFromAgent($dev['user_agent']);
// Reset the number of queries since we're not going to re-instantiate the object
$rebuilder->db->numQueries = 0;
$rebuilder->toLog("Rebuilt cache table.",LOG_NOTICE,"rebuildCacheTable");
// Supporting DB Functions
// truncate or drop+create given table
$TeraWurfl_RIS = "CREATE PROCEDURE [dbo].[". TeraWurflConfig::$TABLE_PREFIX. "_RIS]
DECLARE @wurflid nvarchar(128)
DECLARE @curua nvarchar(255)
WHILE @curlen >= @tolerance
SET @curua = dbo.". TeraWurflConfig::$TABLE_PREFIX. "_EscapeForLike(LEFT(@ua, @curlen))+'%'
SELECT TOP 1 @wurflid=idx.DeviceID
FROM ". TeraWurflConfig::$TABLE_PREFIX. 'Index'. " idx INNER JOIN ". TeraWurflConfig::$TABLE_PREFIX. 'Merge'. " mrg ON idx.DeviceID = mrg.DeviceID
WHERE idx.matcher = @matcher
AND mrg.user_agent LIKE @curua
IF @wurflid IS NOT NULL BREAK
SET @curlen = @curlen - 1
SELECT @wurflid as DeviceID
$TeraWurfl_EscapeForLike = "CREATE FUNCTION ". TeraWurflConfig::$TABLE_PREFIX. "_EscapeForLike
SET @value = REPLACE(@value,'[','[[]');
SET @value = REPLACE(@value,'%','[%]');
SET @value = REPLACE(@value,'_','[_]');
sqlsrv_query($this->dbcon,$TeraWurfl_RIS);
sqlsrv_query($this->dbcon,$TeraWurfl_EscapeForLike);
* Establishes connection to database (does not check for DB sanity)
"PWD"=> TeraWurflConfig::$DB_PASS,
"Database"=> TeraWurflConfig::$DB_SCHEMA
/* Connect using SQL Server Authentication. */
if($this->dbcon === false){
foreach($error_array as $err){$this->errors[]= $err['message'];}
$deletequery = sprintf("DELETE FROM %s WHERE %s = %s", $tablename, 'id', $this->SQLPrep($key));
$query = sprintf("INSERT INTO %s ([%s], [%s]) VALUES (%s, %s)", $tablename, 'id', 'value', $this->SQLPrep($key), $this->SQLPrep($value));
$query = "SELECT [value] FROM ". TeraWurflConfig::$TABLE_PREFIX. 'Settings'. " WHERE [id] = ". $this->SQLPrep($key);
$res = sqlsrv_query($this->dbcon,$query);
// prep raw text for use in queries (adding quotes if necessary)
if($value == '') $value = 'NULL';
else if (!is_numeric($value) || $value[0] == '0') $value = "'" . str_replace("'","''",$value) . "'"; //Quote if not integer
// http://msdn.microsoft.com/en-us/library/ms179859.aspx
$tableres = sqlsrv_query($this->dbcon,"SELECT TABLE_NAME FROM information_schema.tables WHERE Table_Type = 'BASE TABLE'");
while($table = sqlsrv_fetch_array($tableres,SQLSRV_FETCH_NUMERIC))$tables[]= $table[0];
$tableres = sqlsrv_query($this->dbcon,"SELECT COUNT(1) FROM information_schema.tables WHERE Table_Type = 'BASE TABLE' AND TABLE_NAME = ". $this->SQLPrep($tablename));
return ($row[0]> 0)? true: false;
$query = "SELECT COUNT(1) FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[{$func}]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')";
return ($row[0]> 0)? true: false;
$query = "SELECT COUNT(1) FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[{$proc}]') AND type in (N'P', N'PC')";
return ($row[0]> 0)? true: false;
$query = "exec sp_rename '$from', '$to'";
// Rename the primary key to avoid constraint / index name collision
$keyfrom = $to. '.PK_'. $from;
$query = "exec sp_rename '$keyfrom', '$keyto', 'INDEX'";
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.Columns where TABLE_CATALOG = '". TeraWurflConfig::$DB_SCHEMA. "' AND TABLE_NAME = '$table'";
$fieldsres = sqlsrv_query($this->dbcon,$query);
$fieldnames[]= $row['COLUMN_NAME'];
$query = "exec sp_spaceused '$table'";
$stats['rows'] = $row['rows'];
$stats['bytesize'] = intval($row['data']) * 1024;
if(in_array("actual_device_root",$fieldnames)){
$res = sqlsrv_query($this->dbcon,"SELECT COUNT(1) AS devcount FROM $table WHERE actual_device_root=1");
$stats['actual_devices'] = $row['devcount'];
while($ua = sqlsrv_fetch_array($cacheres,SQLSRV_FETCH_NUMERIC))$uas[]= $ua[0];
return isset ($errors[0])? $errors[0]['message']: "none";
$res = sqlsrv_query($this->dbcon,"SELECT @@version Stable 2.1.3 $Date: 2010/09/18 15:43:21
$row = sqlsrv_fetch_array($res);
preg_match('/^([^)]+\))/',$row['server_version'],$matches);
|