Vertica and PDO prepared statements - php

For the last two days, I've been struggling with a very strange bug while I'm connecting with Vertica using PDO. You see, the following script works:
$c = new PDO("odbc:Driver=Vertica;Server=x.x.x.x;Port=5433;Database=db;", "MyUser", "MyPassword");
$c->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $c->prepare("SELECT * FROM myClients WHERE ClientNum = 88");
$stmt->execute();
After that, I loop through the results and display them no problem. This basically means my connection is correct otherwise I wouldn't get anything out of the database. On the other hand, the following makes the Apache server reset the connection completely (when run in Windows, I get a message that Apache crashed):
$c = new PDO("odbc:Driver=Vertica;Server=x.x.x.x;Port=5433;Database=db;", "MyUser", "MyPassword");
$c->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$c->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
//$c->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
try
{
$stmt = $c->prepare("SELECT * FROM myClients WHERE ClientNum = :cl");
$stmt->bindValue(":cl", 88);
$stmt->execute();
while($res = $stmt->fetch(PDO::FETCH_ASSOC))
{
echo $res['noClient'] . "<br>";
}
}
catch(Exception $e)
{
echo $e->getMessage();
}
The problem is present both in Linux and Windows and I'm using Vertica version 7.0.2-1 along with the corresponding ODBC driver. The problem was also present in Vertica 6.1. Can anyone give me a hand with this?
Thanks in advance.
EDIT: I tried to set PDO::ATTR_EMULATE_PREPARES to both true and false without any change.
EDIT: This being a test script, I didn't bother with any error handling. Also, given that the server actually crashes, I doubt it would change anything.
EDIT: Updated the code above to include some basic error handling. Apologies to Kermit for sounding condescending in my earlier comment. Anyway, even with this addition to my code, I still didn't get any message, the server would just silently crash and I'd get a "Connection reset" page. Upon seeing this, I tried querying different tables in my database and on one, instead of a crash, I got the following:
SQLSTATE[HY000]: General error: 50310 [Vertica][Support] (50310) Unrecognized ICU conversion error. (SQLExecute[50310] at ext\pdo_odbc\odbc_stmt.c:254)
EDIT: Went to my ODBC DSN, clicked Configure, went on the Server Setting tab and found that the locale was set to: en_US#collation=binary (which is the default for Vertica, I believe). Should I check somewhere else?
EDIT: I was curious to see what the bindValue() was doing to my query and so opened the vertica.log file. Here's what I saw:
2014-10-02 11:38:42.100 Init Session:0x5ef3030 [Session] <INFO> [Query] TX:0(vertica-1756:0xbc42) set session autocommit to on
2014-10-02 11:38:42.104 Init Session:0x5ef3030 [Session] <INFO> [PQuery] TX:0(vertica-1756:0xbc42) SELECT * FROM myClients WHERE ClientNum = ?
2014-10-02 11:38:42.105 Init Session:0x5ef3030-a00000000aac68 [Txn] <INFO> Begin Txn: a00000000aac68 'SELECT * FROM myClients WHERE ClientNum = ?'
2014-10-02 11:38:42.915 Init Session:0x5ef3030-a00000000aac68 <LOG> #v_flexgroup_node0001: 08006/2895: Could not receive data from client: No such file or directory
2014-10-02 11:38:42.915 Init Session:0x5ef3030-a00000000aac68 <LOG> #v_flexgroup_node0001: 08006/5167: Unexpected EOF on client connection
2014-10-02 11:38:42.915 Init Session:0x5ef3030-a00000000aac68 <LOG> #v_flexgroup_node0001: 00000/4719: Session vertica-1756:0xbc42 ended; closing connection (connCnt 2)
2014-10-02 11:38:42.916 Init Session:0x5ef3030-a00000000aac68 [Txn] <INFO> Rollback Txn: a00000000aac68 'SELECT * FROM myClients WHERE ClientNum = ?'
Apparently, it seems PDO is replacing the placeholders by question marks in the final query. Not all that unexpected, but for some reason, the actual value of the parameter seems to get lost along the way.
EDIT: Following a suggestion, I tried:
$stmt = $c->prepare("SELECT * FROM myClients WHERE ClientNum = :cl");
$stmt->execute(array(":cl" => 88));
But the problem remains the same.

Okay, so after going halfway crazy trying to figure out what was wrong with PDO, I discovered that using PHP odbc module directly worked.
Since all my modules are actually written using PDO and rewriting them was not an option, I ended up writing the following wrapper classes:
class PDOVertica
{
protected $conn;
public function __construct($dsn, $user, $password)
{
$this->conn = odbc_connect($dsn, $user, $password);
}
public function prepare($qry)
{
return new PDOVerticaStatement($this->conn, $qry);
}
public function lastInsertId()
{
$stmt = odbc_prepare($this->conn, "SELECT LAST_INSERT_ID()");
odbc_execute($stmt);
$res = odbc_fetch_array($stmt);
return $res['LAST_INSERT_ID'];
}
}
class PDOVerticaStatement
{
protected $qry;
protected $param;
protected $stmt;
public function __construct($conn, $qry)
{
$this->qry = preg_replace('/(?<=\s|^):[^\s:]++/um', '?', $qry);
$this->param = null;
$this->extractParam($qry);
$this->stmt = odbc_prepare($conn, $this->qry);
}
public function bindValue($param, $val)
{
$this->param[$param] = $val;
}
public function execute()
{
if($this->param == null)
odbc_execute($this->stmt);
else
odbc_execute($this->stmt, $this->param);
$this->clearParam();
}
public function fetch($option)
{
return odbc_fetch_array($this->stmt);
}
protected function extractParam($qry)
{
$qryArray = explode(" ", $qry);
$ind = 0;
while(isset($qryArray[$ind]))
{
if(preg_match("/^:/", $qryArray[$ind]))
$this->param[$qryArray[$ind]] = null;
++$ind;
}
}
protected function clearParam()
{
$ind = 0;
while(isset($this->param[$ind]))
{
$this->param[$ind] = null;
++$ind;
}
}
}
I was pleasantly surprised to find that this works without me having to rewrite hundreds of modules. I do need to rework some of the SQL since there are differences between MySQL and Vertica, but those are just minor touch ups.
Anyway, should anyone choose to use these classes, keep in mind I only implemented what I needed in terms of functionalities and they only work with queries using placeholders for parameters (:someParameter). Use them and modify them at your own discretion.
Thanks for anyone who helped me.

Related

A template code to execute stored procedures

Usually when it is necessary to communicate with MySQL via PHP I use a template similar to the one below (which is similar to the ones available in beginners tutorials):
// Exception Handler
class customException extends Exception {}
// Database Link (include file in a private directory)
function db_connect()
{
$hostname = "localhost";
$username = "username";
$password = "password";
$database = "database";
$connection = mysqli_connect($hostname, $username, $password, $database);
return $connection;
}
// Template for calling common types of stored procedures:
// select a table row based on the primary key (pk)
function select_pk($connection, string $pk): array
{
// if other database is needed
mysqli_select_db($connection, "database1");
// query execution
$query = sprintf("CALL select__pk('%s')", mysqli_real_escape_string($connection, $pk));
$resource = mysqli_query($connection, $query);
$result = mysqli_fetch_assoc($resource);
// prepare for next query
mysqli_free_result($resource);
while(mysqli_more_results($connection)) mysqli_next_result($connection);
// use exception handling if necessary
if(!isset($result)) throw new customException('pk not found');
return $result;
}
// Typical execution
$connection = db_connect();
try
{
$result = select_pk($connection, $pk);
}
catch(customException $e)
{
/*** do something ***/
}
Although this template is, so far, working fine (single server), I have the impression that:
the preparation for next query is overcomplicated (mysqli_free_result, mysqli_more_results and mysqli_next_result)
it does not deal properly with errors
Question
Any comments or advice on how to improve this template?
Well, first I would give a generalized answer that likely would help other people stumbling upon this question and then review the particular case of yours.
I asked myself exactly the same question a long time ago and eventually came to a set of solutions that ease the database operations using mysqli.
Mysqli connection
I have doubts about storing a connection code in a function. It asks to be misused. A connection to a single database should be established strictly once during a single HTTP request/php instance. but a function's purpose to be called multiple times. It would be better to put the connection code in a file instead, and then just include this file in your code in a single place.
I've got a canonical mysqli connection code that deals with a whole lot of problems before they even appear. So, instead of function db_connect() let's create a file called mysqli.php and put the following code there
<?php
$host = '127.0.0.1';
$db = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$conn = new mysqli($host, $user, $pass, $db);
$conn->set_charset($charset);
} catch (\mysqli_sql_exception $e) {
throw new \mysqli_sql_exception($e->getMessage(), $e->getCode());
}
unset($host, $db, $user, $pass, $charset); // we don't need them anymore
Among other solutions it will translate mysql errors into PHP exceptions which is, basically all you need in order to deal with errors.
Running prepared queries
The next problem is rather elaborate code required for the prepared queries in mysqli. To deal with it i wrote a mysqli helper function that eases the process dramatically.
note that although your current approach with mysqli_real_escape_string() is technically safe, it is frowned upon never the less, as it's a subject of human errors of all sorts. Better stick to prepared statements for all queries that involve a PHP variable as input.
So next solution would be a helper function like this
function prepared_query($mysqli, $sql, $params = [], $types = "")
{
if (!$params) {
return $mysqli->query($sql);
}
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt->get_result();
}
and you will get a tool that will make prepared statements as smooth as regular queries
Calling stored procedures with mysqli
Stored procedures are not easy because of a quirk: every call returns more than one resultset and therefore we need to loop over them. We cannot avoid it but at least we can automatize this process too. We can write a function that encapsulates all the resultset jiggery-pokery.
function prepared_call($mysqli, $sql, $params = [], $types = ""): array
{
$resource = prepared_query($mysqli, $sql, $params, $types);
$data = $resource->fetch_all(MYSQLI_ASSOC);
while(mysqli_more_results($mysqli)) mysqli_next_result($mysqli);
return $data;
}
A specifik function to call for a PK
And finally we can rewrite your select_pk() function
function select_pk($mysqli, string $pk): array
{
$data = prepared_call($mysqli, "CALL select__pk(?)", $pk);
return $data[0] ?? null;
}
I am not really sure we need an exception here though:
include 'mysqli.php';
$result = select_pk($mysqli, $pk);
if (!$result) {
/*** do something ***/
}

PDO Sqlite error 5: database locked

I've been trying to use a sqlite database (php with PDO), but have been running into a problem. Generally the commands work, and everything is fine (including storing files), but for some reason when I run these two commands (which have been simplified), I get the error
SQLSTATE[HY000]: General error: 5 database is locked
I've tried for a while, but have been unable to fix whatever is wrong. The code is below.
Things I've done:
Tried to put sleep(2) between commands
Found out that commenting either of the commands out will cause the error not to happen (which doesn't really help, as both commands must run)
Note that (unlike other problems I saw while looking at similar questions) the database operates correctly in other cases.
$db = new MyDB();
$STH = $db->catchMistakes('SELECT PASSWORD FROM USERS WHERE USERNAME = ?', "test");
$STH->fetchColumn();
$db->catchMistakes("UPDATE ISSUES SET NAME = ? WHERE NUM = ?", ["test", "1"]);
And here's the code for MyDB
public function catchMistakes($cmd, $params = []) {
if (!is_array($params)) {
$params = [$params];
}
try {
$DBH = new PDO("sqlite:" . DB);
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$DBH->beginTransaction();
$query = $DBH->prepare($cmd);
$toReturn = $query->execute($params);
$DBH->commit();
return $query;
}
catch(PDOException $e) {
$DBH->rollback();
$error = $e->getMessage();
exit;
}
}
Sorry if there's a simple fix, I'm pretty new at this. Any help would be greatly appreciated.
You can use closeCursor() method on a PDOStatement object to free the connection to the database so the statement can be executed. You can refer to the PHP manual.

Using PDO Query reset my server

I've some trouble with my website. It works perfectly on localhost, but since yesterday I tried to put it on my windows 2003 server. (with apache 2.2.11, mysql 5.1.31, php 5.2.8).
My problem is when I try to use authentification page of my website, server reset.
It reset only if I call the PDO->query function (to get user on my database)
My query code :
function getUser($login)
{
try{
$bdd = connectDB();
$requete = $bdd->query('SELECT * FROM `utilisateur` WHERE `Login` = \''.$login.'\'');
$user = new Utilisateur();
if($donnees = $requete->fetch())
{
$user->setLogin($donnees['Login']);
$user->setAuth($donnees['Role']);
$user->setTrigramme($donnees['Trigramme']);
}
$requete->closeCursor();
disconnectDB($bdd);
return $user;
}catch(Exception $e){
error_log('Exception :
'.$e, 3, ADR_ERROR_LOG);
}
}
Line : ConnectDb works fine, but when it arrive on the net line, navigator (I've tried with IE, Chrome and Firefox) wait a long time and said 'Connexion with the server has been reset', and I've none error message on my error log:(
Thank's in advance
EDIT : I've tried like this, and still have the same problem :
function getUser($login)
{
try{
$bdd = connectDB();
$requete = $bdd->prepare('SELECT * FROM `utilisateur` WHERE `Login` = :login');
// $requete = $bdd->query('SELECT * FROM utilisateur WHERE Login = \''.$login.'\'');
$requete->bindValue('login', $login, PDO::PARAM_STR);
$requete->execute();
$user = new Utilisateur();
if($donnees = $requete->fetch())
{
$user->setLogin($donnees['Login']);
$user->setAuth($donnees['Role']);
$user->setTrigramme($donnees['Trigramme']);
}
$requete->closeCursor();
disconnectDB($bdd);
return $user;
}catch(Exception $e){
error_log('Exception :
'.$e, 3, ADR_ERROR_LOG);
}
}
EDIT 2 This works with this : BUT WHY ???
$db = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
mysql_select_db(DB_NAME,$db);
$sql = 'SELECT * FROM `utilisateur` WHERE `Login` = \''.$login.'\'';
$req = mysql_query($sql) or die('Erreur SQL !<br>'.$sql.'<br>'.mysql_error());
$user = new Utilisateur();
while($donnees = mysql_fetch_assoc($req))
{
$user->setLogin($donnees['Login']);
$user->setAuth($donnees['Role']);
$user->setTrigramme($donnees['Trigramme']);
}
mysql_close();
return $user;
Finally I use mysqli_ to do what I wanted and it works perfectly.
If someone have a solution about this problem, He could put it in order to help the next person.
Thank's to the persons who have search solution.
I think you have just got a typo in your code. With PDO a typo can determine your fate and have you scratching your head for a minute. Look at your prepare query. When you bind the login parameter you never specified the : before login.
Look at this part:
$requete->bindValue('login', $login, PDO::PARAM_STR);
So "login" should be ":login". Just for future reference you don't have to use PDO::PARAM_STR because any parameters bound to the query are automatically handled as strings. If you were binding a numeric value then you'd want to set the data type to PDO::PARAM_INT.
PDO can be tricky - especially when it comes to typos and errors.
Long story short I think the parameter naming issue is all of your problems.

Can't find what's wrong with PDOStatement::Execute =/

I'm starting to build a little PDO wrapper I will be using for my application. However, when I started coding, I bumped into an issue I can't seem to resolve.
The problem I have is that PDOStatement's execute() is returning false and I don't know if there's something wrong with the value binding or the execution. I've tested the query (which anyway is very simple) and it works fine. Connection to the server is also working fine.
I hope you can help! Here's my code:
<?php
class DataBase {
private $PDO;
private static $instancia;
public static function getInstance() {
if (!self::$instancia instanceof self) {
self::$instancia = new self;
}
return self::$instancia;
}
function __construct() {
$configuracion = Configuracion::getInstance();
// echo "mysql:host={$configuracion->dbHost};dbname=mysql", $configuracion->dbUser, $configuracion->dbPassword;
try {
$this->PDO = new PDO("mysql:host={$configuracion->dbHost};dbname=mysql", $configuracion->dbUser, $configuracion->dbPassword);
debug("conectado a la db", __FILE__, __LINE__);
} catch (PDOException $e) {
debug($e->getMessage(), __FILE__, __LINE__);
}
}
function selectDistanceFromDistances($a, $b) {
$sentencia = $this->PDO->prepare('SELECT distance FROM distances WHERE a = ? AND b = ?;');
// debug($sentencia->execute(array($a, $b)));
$sentencia->bindValue(1, 15, PDO::PARAM_INT);
$sentencia->execute();
$this->PDO->errorInfo();
$resultado = $sentencia->fetchAll();
return $resultado;
}
}
?>
Thanks!
$sentencia->execute();
$this->PDO->errorInfo();
You're doing the execute, and then asking for error info, but you aren't actually doing anything with the error info! You seem to have a debug function, so that seems like a good idea to use here.
Your query has two placeholders, but you've only bound one of them, so that's probably what the error is.
You might want to consider turning on exceptions mode and using try/catch. PDO is silent by default, outside of the constructor.

Do I have this PDO Connection Class right?

I've been playing around with PDO for the last few days, I'm working on a small CMS system to teach myself OOP skills, but even though it's only a small CMS, I want it to be able to handle whatever the web can throw at it.
This is what I've come up with so far, I'm going to add connection pooling to the constructor to enable large amounts of concurrent connects on demand. I'm very new to this OOP stuff so I'm wanting a little advise and critism, no doubt I've done something terribly wrong here.
I took the top answer to Global or Singleton for database connection? as the base design, although I've added a private constructor as I want to use $this->dbConnectionInstance throughout the class for numerous helper functions to use.
Thanks very much for your time, I really will appreciate any advise you can give me,
-Drew
// Usage Example: $dbconn = dbManager::getConnection();
// $dbconn->query("SELECT * FROM accounts WHERE id=:id", "':id' => $id");
<?php
class dbManager {
private static $dbManagerInstance;
private $dbConnectionInstance;
private $stime;
private $etime;
public $timespent;
public $numqueries;
public $queries = array();
public static function getManager(){
if (!self::$dbManagerInstance){
self::$dbManagerInstance = new dbManager();
}
return self::$dbManagerInstance;
}
// Server details stored in definition file
private function __construct($db_server=DB_SERVER, $db_user=DB_USER, $db_pass=DB_PASS, $db_params=array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")) {
if(!$this->dbConnectionInstance)
{
try{
$this->dbConnectionInstance = new PDO($db_server, $db_user, $db_pass, $db_params);
$this->dbConnectionInstance->setAttribute(PDO::ATTR_PERSISTENT, PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
$this->dbConnectionInstance = null;
die($e->getMessage());
}
}
return $this->dbConnectionInstance;
}
private function __destruct(){
$this->dbConnectionInstance = null;
}
private function query($sql, $params = array()) {
$this->queries[] = $sql;
$this->numqueries++;
$this->sTime = microtime();
$stmt = $this->dbConnectionInstance->prepare($sql);
$stmt->execute($params);
$this->eTime = microtime();
$this->timespent += round($this->eTime - $this->sTime, 4);
return $stmt;
}
}
?>
Thank you both for your suggestions, I've now added the rollback and commit into my exception handling, I'm just researching the use of buffered queries, I'm not entirely sure what ths will give me?
Looks good, I would add rollback functionality, along with the buffered query/errorInfo suggestions (If you're using a RDBMS that supports transactions):
try {
$this->dbConnectionInstance->beginTransaction();
$stmt = $this->dbConnectionInstance->prepare($sql);
$stmt->execute($params);
$this->dbConnectionInstance->commit();
}catch(PDOException $e){
$this->dbConnectionInstance->rollback();
}
commit() , beginTransaction()
EDIT: added links below for more info on buffered queries:
mysql performance blog
pdo mysql buffered query support
stack overflow: pdo buffered query problem
The code you have dosent look too bad. however if i could make a couple small changes (mainly error handling).
both the prepare and execute statements will return false on error. and you can access the error from $this->dbConnectionInstance->errorInfo() in your example above.
also if you plan on using any large queries I suggest using a buffered query: PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
looks like a good start. Good luck on your CMS.

Categories