I'm trying to diagnose a problem only found on a production system and I'm trying to improve the error reporting for errors thrown by pg_convert(), but not sure where to go. I tried changing the code in the dev environment to:
$converted_values = pg_convert($cnx, $table, $values);
if ($converted_values === FALSE)
throw new Exception("Failed to insert record: '".pg_last_error($cnx)."' using values: ".var_export($values, true));
But pg_last_error() returned nothing.
Note: I tested the above by trying to insert a null value into a NOT NULL column. In addition to getting no joy from pg_last_error() the log had some "PHP Notice"s in it. The production log didn't have any such notices. Just the empty return value from pg_convert() :(
I'm running PostgreSQL 9.4.6 on Debian 8.3 and PHP 5.6.22.
From what I can tell, trying to get anything done w/pg_convert() is a bust. It's not very reliable in dealing w/different data, and it's inconsistent about putting double quotes around column names. If you're using pg_convert() you'll likely want to convert to prepared statements. What they do is very different, so you might have to make fundamental changes to your code base, but we made the change and it seems to be working for us.
Edit: and one such problem that pg_convert()/pg_query() seemed to be able to roll w/that pg_execute() doesn't, is empty strings assigned to integer columns where NOT NULL isn't set. To fix this (at least for now) I had to:
loop through the array passed to pg_execute()
call pg_meta_data() when I found one w/an empty string
determine if the destination column is an integer (be careful: there are probably a lot of ways in which the type for an integer column can be expressed) then if nulls are allowed at all
overwrite the empty string w/null.
Inserting a NULL into a NOT NULL column will not trigger an error in pg_convert. Errors will result from connection problems, a missing table, or a missing column, or data that cannot be converted from the PHP structure to the type in the database (e.g. a word to a number).
Related
I have a conundrum that appears to defy logic, involving Lumen, PHP, PDO, and SQL Server.
I have a controller which contains an action, that executes a stored procedure on a QL Server instance before returning the results as a JSON string. Nothing special is happening but for certain parameters, I do not get any response.
Right, some code. Here's the PHP/PDO prepared statement.
// Prepare our query.
$query = $pdo->prepare("
EXEC [dbase].[dbo].[myStoredProc]
#A = :A,
#B = :B,
#C = :C,
#D = :D,
#E = :E,
#F = :F,
#G = :G
");
// Bind the parameters and execute the query.
$query->bindParam(':A', $A);
$query->bindParam(':B', $B);
$query->bindParam(':C', $C);
$query->bindParam(':D', $D);
$query->bindParam(':E', $E);
$query->bindParam(':F', $F);
$query->bindParam(':G', $G);
$query->execute();
// Uncomment the following line for debugging purposes.
$query->debugDumpParams();
// Lets get all of the data.
$data = $query->fetchAll(\PDO::FETCH_ASSOC);
print_r($data);
Perfectly normal as I said. If I use POSTMAN and pass in the parameters as follows:
A 'C_ICPMS_06'
B 'AQC1'
C '726'
D NULL
E '2021-08-30 00:00:00'
F '2021-11-30 23:59:59'
G NULL
I get a list of results as expected, both from POSTMAN and PHP as well as through SSMS (using the output from the debug statement).
Now if I change parameter C from '726' to '728', I do not get any output from POSTMAN and PHP, but still, get output from SSMS.
Thinking that there could be some text within the output that is breaking the FETCHALL function, I amended the stored procedure to return a single record, all columns containing 1's. Once more the parameter of 726 works, 728 does not.
I added a VAR_DUMP command to ensure that the parameter isn't being molested on its way to the controller, both parameter values report that they are strings with 3 characters in length.
if I change the prepared statement as below, I still don't get any results seen within POSTMAN/PHP.
// Bind the parameters and execute the query.
$query->bindParam(':A', $A);
$query->bindParam(':B', $B);
//$query->bindParam(':C', $C);
$query->bindValue(':C', '728');
$query->bindParam(':D', $D);
$query->bindParam(':E', $E);
$query->bindParam(':F', $F);
$query->bindParam(':G', $G);
$query->execute();
The debug SQL statement is identical to before (using the param as opposed to value).
If I change the stored procedure, such that regardless of what value is passed in for parameter C, it is hardcoded to 728, it works as intended (obviously it does not matter what the parameter is set within POSTMAN). So I get values within POSTMAN and SSMS, therefore, it is safe to assume that the whole problem is being caused by the parameter and value '728'.
Further digging at this issue, I find that if the parameter has a value of '72F' or '70W', also returns no results via POSTMAN/PHP but does from within SSMS. I've checked and cannot see any error messages being produced.
I added the below lines to the controller to see if I can see an issue, but nothing was seen (not on screen nor within error files).
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
In trying to figure this out, I created a temporary database table in order to capture the input parameters and the number of records as found within the SP. This should show where the problem lies, i.e. within PHP or within SQL Server. It now gets stranger.
Calling the SP from within SSMS, it populates with an expected record, including the number of records the initial search returned. However, calling it from POSTMAN using the controller, everything is the same in terms of parameters, but the number of records found is 0!
So I know something very weird is going on, but cannot put my finger on what and therefore how to fix it. If anyone has any ideas or has come across a similar problem, please let me know. this is bugging me now. No doubt when I get this working, it'll be a silly error and I'll end up kicking myself.
The issue was that a temporary table that was being created couldn't hold a specific value being assigned to it. The column was designated as a TINYINT but should have been a SMALLINT since the value could go negative.
Why SSMS never reported that as an issue and happily allowed it through, God only knows. But when called externally, it failed to insert any records within the temporary table, returning no records as a result.
There go 1.5 days of my life never to be seen again.
I have a procedure in my Oracle DB with an array as output parameter. In this procedure I put all the teams with their points into an array.
create or replace package pck_tournament
as
type trranking is record (
position number
, team VARCHAR2(20)
, points number
);
type taranking is table of trranking;
procedure retrieve_ranking (oparray out taranking);
end pck_tournament;
But when I try to call this procedure with PHP I always get an error like this:
PLS-00306: wrong number or types of arguments in call to 'RETRIEVE_RANKING'
This is a part of my PHP code:
$out_arr = array();
$stmt = oci_parse($conn, "BEGIN pck_tournament.retrieve_ranking(:taranking); END;");
oci_bind_array_by_name($stmt,":taranking", $out_arr, 10000, 10000, SQLT_CHR );
oci_execute($stmt);
If I change the OUT parameter to a VARCHAR2 for testing, I'm able to read the result. But I can't manage to make it work if it is an array.
So the problem must be that I use a wrong type of argument to store my OUT parameter?
I've searched a lot of websites but still have no idea how to make this work.
What you have in oracle is not a just an array, it is a array of records.... So a standard array in PHP is not going to be able to handle it.
As per the below question here on Stack Overflow you need to tell PHP what the Type is going to look like
PHP: Binding variable to table type output parameter
So use the below (substituting your type and schema)
$table_output = oci_new_collection($conn,'some_table_type','schema');
The other question also has a link to a good resource for finding more information about this.
As pointed out by #MT0 you will have to change the way that you are defining the types as well. You can iether change it to object as suggested or leave it as record, but the major change will be moving the declaration outside of you package.
PHP will not be able to see them if they are only defined in the package.
I have a strange situation.
Suppose I have a very simple function in php (I used Yii but the problem is general) which is called inside a transaction statement:
public function checkAndInsert($someKey)
{
$data = MyModel::model()->find(array('someKey'=>$someKey)); // search a record in the DB.If it does not exist, insert
if ( $data == null)
{
$data->someCol = 'newOne';
$data->save();
}
else
{
$data->someCol = 'test';
$data->save();
}
}
...
// $db is the instance variable used for operation on the DB
$db->transaction();
$this->checkAdnInsert();
$db->commit();
That said, if I run the script containing this function by staring many processes, I will have duplicate values in the DB. For example, if I have $someKey='pippo', and I run the script by starting 2 processes, I will have two (or more) records with column "someCol" = "newOne". This happens randomly, not always.
Is the code wrong? Should I put some constraint in DB in form of KEYs?
I also read this post about adding UNIQUE indexes to TokuDB which says that UNIQUE KEY "kills" write performance...
The approach you have is wrong. It's wrong because you delegate the authority for integrity/uniqueness check to PHP, but it's the database that's responsible for that.
In other words, you don't have to check whether something exists and then insert. That's bad because there's always some slight ping involved between PHP and MySQL and as you already saw - you can get false results for your checks.
If you need unique values for certain column or combination of columns, you add a UNIQUE constraint. After that you simply insert. If the record exists, insert fails and you can deal with it via Exception. Not only is it faster, it's also easier for you because your code can become a one-liner which is much easier to maintain or understand.
Basically, I need to get some data out of a SQL Server 2005, using PHP.
Previously we used the mssql_* functions, however due to various server issues we are only now able to use odbc_* functions.
The table has various columns whose names have spaces in them, and before it is suggested.... no I cannot change them as this is a totally separate piece of software in another language and it would break it, I am just getting stats out of it.
Anywho, I had previously been accessing these columns by putting their names in square brackets, e.g. [column name] and it worked fine under the mssql_* functions, however when I do this:
$sql = "select top 1 [assessment name] as AssessmentName from bksb_Assessments";
$result = odbc_exec($db, $sql);
while($row = odbc_fetch_array($result))
{
var_dump($row);
}
It prints the results out as:
'assessment name' => string 'Mathematics E3 Diagnostic' (length=25)
So as you can see it is totally ignoring the alias I've given it and still calling it [assessment name].
But if I run the exact same thing in SQL Server Management Studio Express, it works fine and uses the alias.
I've tried various different combinations, such as quoting the alias, quoting the column, different brackets, etc... but no luck so far.
This isn't a huge issue, as I can change what my script uses to look for "assessment name" in the result array instead of the alias, but it's just a bit annoying that I couldn't work out why this was happening...
Cheers! :)
EDIT:
Actually i don't think the square brackets make a difference, just trying to alias any column isn't working with through php odbc, however I can still do something like CAST(whatever) AS 'alias' and that works fine... just not selecting a column as an alias...? :/
This is by no means a perfect solution, and I would love to learn the proper way to deal with this issue, but in the mean time, add +'' to each column, after the underlying column name, but before the AS alias and you should be able to circumvent this issue.
While attempting to troubleshoot this issue and determine why some of my team's stored procedures exhibited this behavior, I finally discovered why. What's strange is that we were only experiencing this issue with a few stored procedures, while most returned the the aliases as expected.
It appears that declaring at least one variable, whether or not it is being used, will prevent this particular bug from occurring. The variable can be of any type.
If you're just executing a SELECT query without using a stored procedure, use a DECLARE statement to define a variable before or after the statement:
// This works...
DECLARE #ignore TINYINT; SELECT EmployeeID AS employee_id FROM Employee;
// So does this...
SELECT EmployeeID AS employee_id FROM Employee; DECLARE #ignore TINYINT;
Now if you're running into this issue with your stored procedures, you can take the same approach above by defining a variable anywhere in the body.
One other thing I noticed is that if the stored procedure is defined with parameters, if your the parameter gets set or evaluated in something like an if statement, this bug will also not occur. For example,
// This works...
CREATE PROC get_employee(
#employee_id VARCHAR(16) = NULL
)
AS
IF (#employee_id IS NOT NULL)
BEGIN
SELECT FirstName AS first_name FROM Employee WHERE EmployeeID = #employee_id;
END
GO
Using SET:
// So does this...
CREATE PROC get_employee(
#employee_id VARCHAR(16) = NULL,
#ignore TINYINT
)
AS
SET #ignore = NULL;
SELECT FirstName AS first_name FROM Employee WHERE EmployeeID = #employee_id;
GO
I still don't quite understand the cause or nature of the bug; but these workarounds, similar to the type-casting hack above, appear to get around this annoying bug.
It's probably also worth mentioning that we didn't encounter this issue in Ubuntu 14.04 and PHP 5.4. As soon as we upgraded to Ubuntu 18.04 and PHP 7.2, that was when we began experiencing this issue. In both instances, we do have FreeTDS configured to use version 7.1, which is why we're baffled by this particular bug.
#dearsina's approach in fact seems to be one of the few ways to handle this bug.
However, this solution is not applicable to all datatypes. So for me, the only way to cover all needed columns was to CAST() the relevant column (where initially c.State would be of datatype bit):
SELECT
CAST(c.State AS nvarchar) AS 'customer_state'
FROM
customers AS c;
as else you might get an error message similar to
The data types bit and varchar are incompatible in the add operator.
Reference on CASTing large data sets: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/643b6eda-fa03-4ad3-85a1-051f02097c7f/how-much-do-cast-statements-affect-performance?forum=transactsql
Some further finding on this topic is a recently (November 2017) raised bug report at bugs.php.net claiming that the problem is related to FreeTDS: https://bugs.php.net/bug.php?id=75534.
This bug report contains an inofficial (and at least from my end untested) patch:
diff --git a/ext/odbc/php_odbc_includes.h b/ext/odbc/php_odbc_includes.h
index 93e2c96..8451bcd 100644
--- a/ext/odbc/php_odbc_includes.h
+++ b/ext/odbc/php_odbc_includes.h
## -292,18 +292,16 ## void odbc_sql_error(ODBC_SQL_ERROR_PARAMS);
#define PHP_ODBC_SQLCOLATTRIBUTE SQLColAttribute
#define PHP_ODBC_SQLALLOCSTMT(hdbc, phstmt) SQLAllocHandle(SQL_HANDLE_STMT, hdbc, phstmt)
-
-#define PHP_ODBC_SQL_DESC_NAME SQL_DESC_NAME
#else
#define IS_SQL_LONG(x) (x == SQL_LONGVARBINARY || x == SQL_LONGVARCHAR)
#define PHP_ODBC_SQLCOLATTRIBUTE SQLColAttributes
#define PHP_ODBC_SQLALLOCSTMT SQLAllocStmt
-
-#define PHP_ODBC_SQL_DESC_NAME SQL_COLUMN_NAME
#endif
#define IS_SQL_BINARY(x) (x == SQL_BINARY || x == SQL_VARBINARY || x == SQL_LONGVARBINARY)
+#define PHP_ODBC_SQL_DESC_NAME SQL_DESC_LABEL
+
PHP_ODBC_API ZEND_EXTERN_MODULE_GLOBALS(odbc)
#define ODBCG(v) ZEND_MODULE_GLOBALS_ACCESSOR(odbc, v)
Faced similar problem. The 4th parameter of odbc_connect did the trick in my case choosing cursor type SQL_CUR_USE_ODBC.
I have a MYSQL table with an ENUM field named "offset" and some other columns. The field is defined as:
ENUM(0,1), can be NULL, predefined value NULL
Now I have two server. A production server and a development server and the same PHP script used to create and to update the database.
First step: the application create the record witout passing the "offset" in the CREATE query.
Second step: the application ask to the user some data (not the "offset" value), read the row inserted in step one and make an array, update some field (not the "offset" field), create a query in an automated fashion and save the row again with the updated values.
The automated query builder simple read all the field passed in an array and create the UPDATE string.
In both systems I obtain this array:
$values = array(... 'offset' => null);
and convert it in this same query passing the values in the mysql_real_escape_string:
UPDATE MyTable SET values..., `offset` = '' WHERE id = '10';
Now there is the problem. When i launch the query in the production system, the row is saved, in the development system I got an error and the db says that the offset data is wrong without saving the row.
From phpmyadmin when I create the row with the first step, it shows NULL in the offset field. After saving the field in the system which give no errors, it show me an empty string.
Both system are using MySQL 5 but the production uses 5.0.51 on Linux and development use 5.0.37 on Windows.
The questions:
Why one system give me an error an the other one save the field ? Is a configuration difference ?
Why when I save the field which is an enum "0" or "1" it saves "" and not NULL ?
Why one system give me an error an the other one save the field ? Is a configuration difference ?
Probably. See below.
Why when I save the field which is an enum "0" or "1" it saves "" and not NULL ?
According to the MySQL ENUM documentation:
The value may also be the empty string ('') or NULL under certain circumstances:
If you insert an invalid value into an ENUM (that is, a string not present in the list of permitted values), the empty string is inserted instead as a special error value. This string can be distinguished from a "normal" empty string by the fact that this string has the numeric value 0. ...
If strict SQL mode is enabled, attempts to insert invalid ENUM values result in an error.
(Emphasis added.)
strager's answer seems like a good explanation on why your code behaves differently on the 2 environments.
The problem lies elsewhere though. If you want to set a value to NULL in the query you shound use exactly NULL, but you are using mysql_real_escape_string() which result is always a string:
$ php -r 'var_dump(mysql_real_escape_string(null));'
string(0) ""
You should handle this differently. E.g:
$value = null
$escaped_value = is_null($value) ? "NULL" : mysql_real_escape_string($value);
var_dump($escaped_value);
// NULL
Some DB layers, like PDO, handle this just fine for you.
If you want it to be NULL, why don't you do this in the first place:
UPDATE MyTable SET values..., `offset` = NULL WHERE id = 10;