SQLSRV with Linked Server - php

My issues basically revolves around me needing/preferring to use PHP's sqlsrv to access a sql-server 2000 database. I've worked on the project already using that with a sql server 2005 to run all the queries through and switching to something like the ODBC PHP drivers would be a pretty big headache right now. So now I have the original SQL Server 2000 database and 2005 installed on the same computer and I've created a linked server between the 2. Testing it out in Management Studio Express worked by running a simple query to one of the tables.
Now, I'm using the exact same query in PHP using sqlsrv_query and running into an error. My PHP code to test this out looks like this
$connectionOptions = array("UID"=>"user","PWD"=>"password");
$res = sqlsrv_connect("(local)\SQLExpress", $connectionOptions);
if(!$res) die("ERRORS : " . print_r(sqlsrv_errors()));
echo "SELECT * FROM [ServerName].DB.dbo.Table<br/>";
$res = sqlsrv_query($res,"SELECT * FROM [ServerName].DB.dbo.Table");
if($res===false){
die("ERRORS : " . print_r(sqlsrv_errors()));
}else{
var_dump($res);
$ary = sqlsrv_fetch_array($res);
}
var_dump($ary);
echo "<hr>";
var_dump(sqlsrv_errors());
The problem with this code is that the result of of sqlsrv_query doesn't return false but returns resource(11) of type unknown. So running fetch_array on that result tells me that an invalid parameter was passed to sqlsrv_fetch_array. I'm not sure what to do at this point. Is there just a problem running a query on a linked server through sqlsrv?

seems no error in your code.
Please try the fetch_array in a loop and update the result.
while($row = sqlsrv_fetch_array($result))
{
echo($row['field_name']);
}
also remove var_dump($ary);
Usually even if there is any error in the data display using '$row', the error message will show problem with 'sqlsrv_fetch_array'.

Well I figured out the problem. In my haste to make some code to test out whether or not the linked server worked, I just used the same variable name for the result of sqlsrv_connect and sqlsrv_query. Turns out, that was the whole problem. I switched the variable names so that the connection object is $Link and the query stays as $res. Now, I get the access to the database that I was trying to get through 2005 into 2000. So in the future, I definitely will name my variables a bit more carefully so I don't bang my head against the wall for hours.

Related

Why are all of my images getting cut off when I pull the binary out of a SQL Server DB?

I'm using PDO in PHP 7 on an Amazon Linux AMI to connect to a SQL Server DB running on Amazon RDS.
Thus far, I've been able to extract all of the data out of the DB just fine except for one column in one table. The column is of the type image and contains binary data.
When I attempt to use PDO in PHP to select from the column in question and then output the binary to the browser window with the necessary header sent (e.g., header('Content-Type: image/jpeg');), I can only see a part of the file (in the case of an image) or I get a corrupt-file error (in the case of a PDF).
In a few edge cases (I'm presuming when the file size is quite small), I can see the full image, but that's rare.
The following is an example of this problem in Chrome:
I've done some pretty extensive Googling on this issue, but there doesn't seem to be a lot of info on this issue, and all the pages I've seen are quite old and related to either mssql (no longer available in PHP 7) or sqlsrv drivers.
Here are some examples of (seemingly) related pages I've found:
https://www.reddit.com/r/PHP/comments/m6v1t/need_help_displaying_jpg_from_mssql_image_field/
https://css-tricks.com/forums/topic/blob-display-is-truncated/
PHP is truncating MSSQL Blob data (4096b), even after setting INI values. Am I missing one?
Does anyone have any ideas as to why this is happening and how I can fix it? I feel like PDO should be able to handle this, but if there is an issue with PDO and I need to use some other drivers to handle this, that's fine too.
Thank you.
I finally found the answer. The data coming back to PHP via PDO was in fact being truncated at 64k, thus causing issues.
Alex helped lead me on the right path by suggesting setting TEXTSIZE to -1. The rookie mistake that I made was that I did SET TEXTSIZE -1 from Microsoft SSMS, assuming that it would be set globally for all connections, which was not the case. It only set it for the SSMS connection, thus the problem.
However, when I finally did the following in PDO in PHP, that is, set TEXTSIZE to -1 with the PDO connection and then make the query from PDO, I was able to set TEXTSIZE for the PDO connection and then get back all the data:
doSqlServerQuery("SET TEXTSIZE -1;");
$results = doSqlServerQuery("SELECT binary-data-image-column AS data
FROM table-name
WHERE key = 95948578934578934;");
//Function definition
function doSqlServerQuery($query, $dbh = null) {
if (!isset($dbh)) {
global $dbh;
}
$stmt = $dbh->prepare($query);
if ($stmt) {
$stmt->execute();
$results = [];
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$results[] = $result;
}
return $results;
} else {
pO($dbh->errorInfo());
}
}
Just fixed this myself, it is being cut off after 64KB has been uploaded, to fix this, either set a limit at 64KB or change it to a longblob, which gives you 4 gigabytes of storage instead of 64 kilobytes.

PDO dblib over freetds resets fetch of query on sql server 2000 if another query is issued inside a fetch loop

Ok, so we've got a new server with
Debian Wheezy 32BIT
PHP 5.5.18
FreeTDS 0.91
This PHP app needs to talk to an old SQL server 2000 server. We used the old code from our previous server (PHP 5.2 and older FreeTDS - can't get the version).
We connect to SQL server 2000 through PDO using dblib driver.
We're experiencing weird behaviour with the fetch function. Basically if we issue a query during a fetch loop on the same pdo connection object, the main query gets reset and next fetch call will return false even if there are still records to be fetched.
// PSEUDO CODE
// Here the main query
$q = $sql7->query("SELECT TOP 5 * FROM News ORDER BY Data Desc");
while ($row = $q->fetch(PDO::FETCH_ASSOC)) {
// Looping through the results
echo "<h1>Main query</h1>";
print_r($row);
// Issue a query on the same pdo connection
$subq = $sql7->query("SELECT TOP 1 * FROM News WHERE IDNews = " . $row['IDNews'] . " ");
while ($subResult = $subq->fetch(PDO::FETCH_ASSOC)) {
echo "<h1>Inner query</h1>";
print_r($subResult);
}
// Here the main query $q->fetch(PDO::FETCH_ASSOC) will answer false on the next iteration
// if we remove the subq, the main query loops just fine
echo "<hr>";
}
Same code on a Windows PHP with pdo_sqlserver driver works just fine.
It doesn't matter the type of fetch that we pass as argument of fetch function.
PHP doesn't throw any warning or error.
I really don't know what's going on here.
As of: reference (PHP BUG SITE)
This is the behavior of MSSQL (TDS), DBLIB and FreeTDS. One statement
per connection rule. If you initiate another statement, the previous
statement is cancelled.
The previous versions buffered the entire result set in memory leading
to OOM errors on large results sets.
So, it seems that it was the previous versions of PHP (5.3 and previous) that were not conforming to the TDS behaviour.
We need to refactor the code then.

I'm getting "String data, right truncation" errors from PHP using ODBC and connecting to a Microsoft SQL Server 2008R2 instance

I am using PHP 5.3.3 on a CentOS 6.2 box, connecting to an instance of Microsoft SQL Server 2008R2. The connection works, and I am able to retrieve data, so long as my queries contain no parameters. When I add parameters, I get the error, "String data, right truncation".
Here's some example code:
<?php
$dbh = new PDO("odbc:myDSN", 'myUsername', 'myPassword');
$testCase = 1;
switch ($testCase) {
case 1:
// This case fails with this error:
// Error 22001: [Microsoft][ODBC Driver 11 for SQL Server]String data, right truncation (SQLExecute[0] at /builddir/build/BUILD/php-5.3.3/ext/pdo_odbc/odbc_stmt.c:254)
$query = "select * from [myDatabase].[sys].[objects] WHERE (([name]=?))";
$stmt = $dbh->prepare($query);
$param1 = 'testtable1';
$stmt->bindParam(1, $param1, PDO::PARAM_STR); // Note: '1' is correct; it should not be '0'
break;
case 2:
// This case works properly
$query = "select * from [myDatabase].[sys].[objects] WHERE (([name]='testtable1'))";
$stmt = $dbh->prepare($query);
break;
}
$execResult = $stmt->execute();
if ($execResult) {
print "Success!\n";
} else {
$errorInfo = $stmt->errorInfo();
print "Error " . $stmt->errorCode() . ": " . $errorInfo[2] . "\n";
}
$rowCount = 0;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "Row " . $rowCount . ":\n";
foreach ($row as $key => $value) {
printf(" %-20s %s\n", $key, $value);
}
$rowCount++;
}
Note that both test cases in the code above should do the same thing. Test case 1 uses parameters (as all code should), and test case 2 explicitly puts the relevant value in the SQL query. Test case 2 works. Test case 1 does not. I have tried replacing 'bindParam()' with 'bindValue()', but this has no effect. I've also tried using named parameters (e.g., :name) instead of positional parameters, but this also has no effect. I've tried adding an explicit length argument to bindParam() (using strlen($param1) as a value), but that gives a really bizarre error message (Incorrect syntax near 'OUTPUT'), and I can only assume that I'm doing it wrong. Integer parameters work properly. Only string parameters fail.
Any ideas why this isn't working?
Of course it's possible that there's a bug in the ODBC driver, or that it's not compatible with my version of PHP, or any number of similar problems, but I hope that I'm simply using the API improperly.
Edit:
Per Anda Iancu's suggestion, I delved into SQL Server Profiler. When traced, case 1 gives two nearly-identical records, one of class SQL:BatchStarting, and one of class SQL:BatchCompleted, both containing the text:
set fmtonly on select [name] from [myDatabase].[sys].[objects] where 1=2 set fmtonly off
Case 2 gives two records, both of class "RPC:Completed". The first contains the text:
declare #p1 int
set #p1=1
exec sp_prepexec #p1 output,NULL,N'select * from [myDatabase].[sys].[objects] WHERE (([name]=''testtable1''))'
select #p1
and the second contains the text:
exec sp_unprepare 1
Update:
In a desperate move, hoping there might be some kind of problem with dropping a new version of unixODBC into an existing version of PHP, I recompiled PHP from source. This turns out to be harder than you might think, on CentOS. Unfortunately, this had no effect. Same errors all around.
After much tweaking and searching, and a whole lot of shot-in-the-dark troubleshooting, I finally decided that this is an ODBC driver problem.
Specifically, I was using a driver downloaded from Microsoft, supposedly designed to work with PHP and unixODBC on RHEL/CentOS6. It's known as "Microsoft ODBC Driver 11 for SQL Server" in its own README file, and comes in a file called msodbcsql-11.0.2270.0.tar.gz. (These details provided for the benefit of anyone else trying to do the same thing)
In light of my experience, I do not recommend this driver.
I downloaded, compiled, and installed the latest "stable" version of FreeTDS instead. If it matters to you, the version I got is 0.91 (the download file doesn't say this, but it unpacks into a directory with this number). This had/has its own minor configuration problems, but ultimately seems to be working much better than the Microsoft-provided driver. I don't know if this is still being actively maintained, as the most recent timestamps in the distribution were August 17, 2011.
Silly me, thinking that I should use the Microsoft driver to access a Microsoft database server, and expect it to actually do what it says it will do.
I think you have probably encountered a bug in Microsoft's ODBC driver, but it is worth pointing out that pdo_odbc has a critical bug related to the bindValue() function. The bug only affects 64-bit but - ha! - Microsoft's ODBC driver is only 64-bit.
It's been a while, but I think which bug you hit depends on your data types and the SQL statement.

Why is my script missing the first record of a recordset?

I got a small PHP script retrieving value from a database. It works but it is just missing the very first record of the recordset.
Here is the code :
$conn = odbc_connect("database","user","passwd");
if (!$conn) {
echo $php_errormsg;
die("Connection failed");
}
$sql = "EXEC dbo.pr_stored_procedure param1, param2";
echo ("sql=". $sql."\n");
$rs=odbc_exec($conn,$sql);
while(odbc_fetch_row($rs)) {
$item1 = odbc_result($rs,"item1");
$item2 = odbc_result($rs,"item2");
echo($item1 . " " . $item2);
}
odbc_close($conn);
I don't understand why it would skip the first row as this is very basic.
It may be bug in the ODBC driver or other library you use.
Do you have problems with similar queries executed in that environment or when you change params to return different dataset?
You have written the this query works from ASP, but also try to execute it in other ODBC environment. My favorite is ActivePython with odbc module or Jython with JDBC-ODBC bridge. You will find examples of such code in my question: How to get trailing spaces from varchar column in Informix using ODBC
Maybe there is something special with first row? Example: I have seen Informix ODBC driver for Linux that returned empty data for first row if first column was datetime.
As soon as you see this query working in other ODBC environment try to enable ODBC tracing and compare trace from working environment with trace from PHP.

Why does this PHP code hang on calls to mysql_query()?

I'm having trouble with this PHP script where I get the error
Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/vhosts/richmondcondo411.com/httpdocs/places.php on line 77
The code hangs here:
function getLocationsFromTable($table){
$query = "SELECT * FROM `$table`";
if( ! $queryResult = mysql_query($query)) return null;
return mysql_fetch_array($queryResult, MYSQL_ASSOC);
}
and here (so far):
function hasCoordinates($houseNumber, $streetName){
$query = "SELECT lat,lng FROM geocache WHERE House = '$houseNumber' AND StreetName = '$streetName'";
$row = mysql_fetch_array(mysql_query($query), MYSQL_ASSOC);
return ($row) ? true : false;
}
both on the line with the mysql_query() call.
I know I use different styles for each code snippet, it's because I've been playing with the first one trying to isolate the issue.
The $table in the first example is 'school' which is a table which definitely exists.
I just don't know why it sits there and waits to time out instead of throwing an error back at me.
The mysql queries from the rest of the site are working properly. I tried making sure I was connected like this
//connection was opened like this:
//$GLOBALS['dbh']=mysql_connect ($db_host, $db_user, $db_pswd) or die ('I cannot connect to the database because: ' . mysql_error());
if( ! $GLOBALS['dbh']) return null;
and it made it past that fine. Any ideas?
Update
It's not the size of the tables. I tried getting only five records and it still timed out. Also, with this line:
$query = "SELECT lat,lng FROM geocache WHERE House = '$houseNumber' AND StreetName = '$streetName'";
it is only looking for one specific record and this is where it's hanging now.
It sounds like MySQL is busy transmitting valid data back to PHP, but there's so much of it that there isn't time to finish the process before Apache shuts down the PHP process for exceeding its maximum execution time.
Is it really necessary to select everything from that table? How much data is it? Are there BLOB or TEXT columns that would account for particular lag?
Analyzing what's being selected and what you really need would be a good place to start.
Time spent waiting for mysql queries to return data does not count towards the execution time. See here.
The problem is most likely somewhere else in the code - the functions that you are blaming are possibly called in an infinite loop. Try commenting out the mysql code to see if I'm right.
Does your code timeout trying to connect or does it connect and hang on the query?
If your code actually gets past the mysql_query call (even if it has to wait a long time to timeout) then you can use the mysql_error function to determine what happened:
mysql_query("SELECT * FROM table");
echo mysql_errno($GLOBALS['dbh']) . ": " . mysql_error($GLOBALS['dbh']) . "\n";
Then, you can use the error number to determine the detailed reason for the error: MySQL error codes
If your code is hanging on the query, you might try describing and running the query in a mysql command line client to see if it's a data size issue. You can also increase the maximum execution time to allow the query to complete and see what's happening:
ini_set('max_execution_time', 300); // Allow 5 minutes for execution
I don't know about the size of your table, but try using LIMIT 10 and see if still hangs.
It might be that your table is just to big to fetch it in one query.
Unless the parameters $houseNumber and $streetName for hasCoordinates() are already sanitized for the MySQL query (very unlikely) you need to treat them with mysql_real_escape_string() to prevent (intentional or unintentional) sql injections. For mysql_real_escape_string() to work properly (e.g. if you have changed the charset via mysql_set_charset) you should also pass the MySQL connection resource to the function.
Is the error reporting set to E_ALL and do you look at the error.log of the webserver (or have set display_erorrs=On)?
Try this
function hasCoordinates($houseNumber, $streetName) {
$houseNumber = mysql_real_escape_string($houseNumber);
$streetName = mysql_real_escape_string($streetName);
$query = "
EXPLAIN SELECT
lat,lng
FROM
geocache
WHERE
House='$houseNumber'
AND StreetName='$streetName'
";
$result = mysql_query($query) or die(mysql_error());
while ( false!==($row=mysql_fetch_array($result, MYSQL_ASSOC)) ) {
echo htmlspecialchars(join(' | ', $row)), "<br />\n";
}
die;
}
and refer to http://dev.mysql.com/doc/refman/5.0/en/using-explain.html to interpret the output.
-If you upped the execution time to 300 and it still went through that 300 seconds, I think that by definition you've got something like an infinite loop going.
-My first suspect would be your php code since mysql is used to dealing with large sets of data, so definitely make sure that you're actually reaching the mysql query in question (die right before it with an error message or something).
-If that works, then try actually running that query with known data on your database via some database gui or via the command line access to the database if you have that, or replacing the code with known good numbers if you don't.
-If the query works on it's own, then I would check for accidental sql injection coming from with the $houseNumber or $streetName variables, as VolkerK mentioned.

Categories