Hi all I think I designed my pdo mysql class rather badly, as I'm not able to put a query inside a while loop of another query because the new query inside the loop wipes out the old one, is there an easy way to fix this?
I'm too used to the really old style of php/mysql where you make a query, then assign that query inside the fetch($query) which you don't do with PDO. This has me in a muddle and it's been bugging me for too long.
My Code cut to the relevant parts
class mysql
{
// the query counter
public $counter = 0;
// the database connection
public $database;
// statement handler
public $STH;
// store all the queries for debugging
public $queries = '';
public function __construct($database_host, $database_username, $database_password, $database_db)
{
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
);
$this->database = new PDO("mysql:host=$database_host;dbname=$database_db", $database_username, $database_password, $options);
$this->database->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
// A plain query
public function pquery($sql, $page = NULL)
{
global $core;
try
{
$this->STH = $this->database->prepare($sql);
$this->counter++;
$this->queries .= "<br />$sql";
return $this->STH->execute();
}
catch (Exception $e)
{
$core->message("Something went wrong. The admin will be notified and punished muhahaha for I am an evil overlord...I'm sure I will be fixed soon.", NULL, 1);
$this->pdo_error($e->getMessage(), $page, $sql);
}
}
// the main sql query function
public function sqlquery($sql, $objects = array(), $page = NULL, $referrer = NULL)
{
global $core;
try
{
$this->STH = $this->database->prepare($sql);
foreach($objects as $k=>$p)
{
// +1 is needed as arrays start at 0 where as ? placeholders start at 1 in PDO
if(is_numeric($p))
{
// we need to do this or else decimals always seem to end up 'x.00', pdo has no decimal check, odd
if ($this->contains_decimal($p) == true)
{
$this->STH->bindValue($k+1, $p, PDO::PARAM_STR);
}
else
{
$this->STH->bindValue($k+1, (int)$p, PDO::PARAM_INT);
}
}
else
{
$this->STH->bindValue($k+1, $p, PDO::PARAM_STR);
}
}
$this->counter++;
$this->queries .= "<br />$sql";
return $this->STH->execute();
}
catch (Exception $e)
{
$core->message("Something went wrong. The admin will be notified and punished muhahaha for I am an evil overlord...I'm sure I will be fixed soon.", NULL, 1);
$this->pdo_error($e->getMessage(), $page, $sql, $referrer);
}
}
public function fetch()
{
$this->STH->setFetchMode(PDO::FETCH_ASSOC);
return $this->STH->fetch();
}
Related
This has nothing to do with Frameworks because it is an external project, I am currently developing to learn, some things about database management or how databases behave...
I need help with some php and PDO... Previously I dev this script:
<?php
class DataBaseManager
{
public function GetData($query, $user, $pass)
{
try {
$db_result = [];
$conn = new PDO("mysql:host=localhost;dbname=test", $user, $pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->exec("set names utf8");
reset($query);
$db_name = key($query);
$conn->exec('USE ' . $db_name);
$db_result['r'] = $conn->query($query[$db_name], PDO::FETCH_ASSOC);
$count = $db_result['r']->rowCount();
$db_result['c'] = $count;
if (0 == $count) {
$db_result['r'] = null;
} elseif (1 == $count) {
$db_result['r'] = $db_result['r']->fetch();
}
return $db_result;
} catch (PDOException $e) {
echo $e->getMessage();
return false;
}
}
public function DeleteData($query, $user, $pass)
{
try {
$conn = new PDO("mysql:host=localhost;dbname=test", $user, $pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->beginTransaction();
$conn->exec("set names utf8");
foreach ($query as $db_name => $query_arr) {
$conn->exec('USE ' . $db_name);
foreach ($query_arr as $key => $query_string) {
$conn->exec($query_string);
++$ct;
}
}
$conn->commit();
$conn = null;
return '<b>' . $ct . ' Records Deleted Successfully.</b>';
} catch (PDOException $e) {
$conn->rollback();
echo $e->getMessage();
return false;
}
}
public function SetData($query, $user, $pass)
{
try {
$db_result = [];
$conn = new PDO("mysql:host=localhost;dbname=test", $user, $pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->beginTransaction();
$conn->exec("set names utf8");
$count = ['u' => 0, 'i' => 0];
foreach ($query as $db_name => $query_arr) {
$conn->exec('USE ' . $db_name);
foreach ($query_arr as $key => $query_string) {
$cq = $conn->exec($query_string);
if (strpos($query_string, 'UPDATE') !== false) {
$count['u'] += $cq;
}
if (strpos($query_string, 'INSERT') !== false) {
$count['i'] += $cq;
}
}
}
$conn->commit();
$db_result['r'] = true;
$db_result['t'] = 'Updates: ' . $count['u'] . ', Inserts: ' . $count['i'];
return $db_result;
} catch (PDOException $e) {
$conn->rollback();
echo $e->getMessage();
return false;
}
}
}
I would like to be able to insert data by volumes in multiple tables in a single commit...
The idea of doing it that way is because if something fails I want it to be automatically rolled back in all table instances...
So I have a data structure in an array with the following content in my new class:
$dbquery =[
'test'=>[
'0' =>[
0 => 'INSERT INTO table_name_1(column1,column2) VALUES (?,?)', //mean it is the query
1 =>[value11,value12], // mean it is a row
2 =>[value21,value22], // mean it is a row
],
'1' =>[
0 => 'INSERT INTO table_name_2(column1,column2,column3,column4) VALUES (?,?,?,?)', //mean it is the query
1 =>[value11,value12,value13,value14], // mean it is a row
2 =>[value21,value22,value23,value24], // mean it is a row
],
],
];
but i have my first try with bindValue, this is my new class mentioned:
<?php
class DataBase
{
private static ?DataBase $instance = null;
public static function getInstance(): DataBase
{
if (!self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}
private array $config;
public function __construct()
{
$this->config = ['DB_HOST'=>'test','DB_NAME'=>'test','DB_USER'=>'test','DB_PASSWORD'=>''];
$this->setConnection(new PDO("mysql:host=" . $this->config['DB_HOST'] . ";dbname=" . $this->config['DB_NAME'], $this->config['DB_USER'], $this->config['DB_PASSWORD']));
}
private PDO $connection;
/**
* #return PDO
*/
private function getConnection(): PDO
{
return $this->connection;
}
/**
* #param PDO $connection
*/
private function setConnection(PDO $connection): void
{
$this->connection = $connection;
}
public function changeConnectionServer(string $host, string $db_name, string $user, string $password): void
{
$this->setConnection(new PDO("mysql:host=" . $host . ";dbname=" . $db_name, $user, $password));
}
private array $query;
public function setDataBaseTarget(string $db_name)
{
if (empty($this->query)) {
$this->query = [];
}
$this->query[$db_name] = [];
}
public function buildQuery(string $query)
{
if (empty($this->query)) {
$this->query = [];
$this->query[$this->config['DB_NAME']] = [];
}
$target = array_key_last($this->query);
$this->query[$target][] = [$query];
}
public function addQueryData($data)
{
$target = array_key_last($this->query);
$key = array_key_last($this->query[$target]);
$this->query[$target][$key][] = $data;
}
private function getQuery(): array
{
return $this->query;
}
/**
* #throws Exception
*/
public function setData(): array
{
try {
$time = -microtime(true);
$con = $this->getConnection();
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$con->beginTransaction();
$con->exec("set names utf8;");
foreach ($this->getQuery() as $db_name => $query_arr) {
$con->exec('USE `' . $db_name . '`;');
$ct = 0;
// on this section have proble with code and logic .... i dont know what i need to dev to insert the data
foreach ($query_arr as $query_structure) {
foreach ($query_structure as $key => $raw) {
if ($key === 0) {
$ct++;
$stmt[$ct] = $con->prepare($raw);
} else {
if (is_array($raw)) {
$c = 0;
foreach ($raw as $value) {
$c++;
$stmt[$ct]->bindValue($c, $value, $this->getParamType($value));
}
}
}
}
$stmt[$ct]->execute();
}
//end section
}
//$con->commit();
return true;
} catch (PDOException $e) {
$con->rollback();
echo $e->getMessage();
return false;
}
}
private function getParamType($value)
{
if (is_int($value)) {
return PDO::PARAM_INT;
} elseif (is_bool($value)) {
return PDO::PARAM_BOOL;
} elseif (is_null($value)) {
return PDO::PARAM_NULL;
} elseif (is_string($value)) {
return PDO::PARAM_STR;
} else {
return false;
}
}
}
$db_handler = DataBase::getInstance();
$db_handler->buildQuery("INSERT INTO `client_list`(`email`,`mobile`) VALUES ('?','?');");
$db_handler->addQueryData(['mail1#test.com', '35634634636546']);
$db_handler->addQueryData(['mail2#test.com', '35634634636546']);
$db_handler->addQueryData(['mail3#test.com', '35634634636546']);
$db_handler->setData();
I can't figure out, develop the part that allows me to package everything in a single transaction... what I have is a stm[] ...
Can someone help me with this development?
I can't make heads or tails of all the things your class is doing, but here's a generalized version according to the bits that seem obvious:
public function runMultipleQueries() {
$dbh = $this->getConnection();
// ... setup stuff
$dbh->beginTransaction();
try {
foreach($this->queries as $query) {
$stmt->prepare($query->queryString);
$param_id = 1;
foreach($query->params as $param) {
$stmt->bindValue($param_id++, $param);
}
$stmt->execute();
}
} catch( \Exception $e ) {
$dbh->rollback();
throw $e;
// IMO if you cannot fully/meaningfully recover, just re-throw and let it kill execution or be caught elsewhere where it can be
// otherwise you're likely having to maintain a stack of if(foo() == false) { ... } wrappers
}
$dbh->commit();
}
Additionally, singleton DB classes have the drawbacks of both being limiting if you ever need a second DB handle, as well as boiling down to being global state, and subject to the same "spooky action at a distance" problems.
Consider using dependency inversion and feeding class dependencies in via constructor arguments, method dependencies via method arguments, etc. Eg:
interface MyWrapperInterface {
function setQueries(array $queries);
function runQueries();
}
class MyDbWrapper implements MyWrapperInterface {
protected $dbh;
public function __construct(\PDO $dbh) {
$this->dbh = $dbh;
}
public function setQueries(array $queries) { /* ... */ }
public function runQueries() { /* ... */ }
}
class MyOtherThing {
protected $db;
public function __construct( MyWrapperInterface $db ) {
$this->db = $db;
}
// ...
}
$wrapper1 = new MyDbWrapper(new PDO($connstr1, $options));
$wrapper2 = new MyDbWrapper(new PDO($connstr2, $options));
$thing1 = new MyOtherThing($wrapper1);
$thing2 = new MyOtherThing($wrapper2);
$thing1_2 = new MyOtherThing($wrapper1);
// etc
I am in the process of learning PDO and am trying to implement it in my current project. When I used mysqli, I could get detailed info about any error using mysqli_error($connection). I googled at what the comparable for PDO would be and found this post, and decided to implement it in my code. However, I am unable to get any error messages even when I know there is an obvious error in the sql statement.
Relevant code:
class Database {
public $connection;
function __construct() {
$this->open_database_connection();
}
public function open_database_connection() {
try {
$this->connection = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PASSWORD);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection->setAttribute( PDO::ATTR_EMULATE_PREPARES, false);
} catch(PDOException $e) {
echo $e->getMessage();
die('Could not connect');
}
} // end of open_database_connection method
public function query($sql, $params = []) {
try {
$query = $this->connection->prepare($sql);
} catch (Exception $e) {
var_dump('mysql error: ' . $e->getMessage());
}
foreach($params as $key => $value) {
if ($key === ':limit') {
$query->bindValue($key, $value, PDO::PARAM_INT);
} else {
$query -> bindValue($key, $value);
}
}
try {
$query->execute();
} catch(Exception $e) {
echo 'Exception -> ';
var_dump($e->getMessage());
}
/*
DID NOT WORK:
if (!$query->execute()) {
print_r($query->errorinfo());
}*/
$result = $query->fetchAll(PDO::FETCH_ASSOC);
$this->confirm_query($result);
return $result;
} // end of query method
function confirm_query($query) {
if (!$query) {
die('mysql error: ');
}
}
When I run the code, I do get the "mysql error", but I do not get any details about it. What am I doing wrong?
Update: As requested, I am providing additional details below.
What I am trying to do is get the user's login detail to be verified. To that end, once the user inputs his credentials , this code runs:
if (isset($_POST['submit'])) {
$username = trim($_POST['username']);
$password = trim($_POST['password']);
//check the database for the user
$user_found = User::verify_user($username, $password);
Relevant code from the User class:
public static function verify_user($username, $password) {
global $db;
$username = $db->escape_string($username);
$password = $db->escape_string($password);
$values = [ ":username" => $username,
":password" => $password,
":limit" => 1
];
$result_array = self::find_this_query("SELECT * FROM users WHERE username=:username AND password=:password LIMIT :limit", true, $values);
return !empty($result_array)? array_shift($result_array) : false;
}
public static function find_this_query($sql, $prepared = false, $params = []) {
global $db;
$the_object_array = array();
$result = $db->query($sql, $params);
$arr_length = count($result);
for ($i = 0; $i < $arr_length; $i++) {
$the_object_array[] = self::instantiation($result[$i]);
}
return $the_object_array;
}
public static function instantiation($the_record) {
$the_object =new self; //we need new self because $the_record corresponds to one user!
foreach($the_record as $the_attribute => $value) {
if ($the_object->has_the_attribute($the_attribute)) {
$the_object->$the_attribute = $value;
}
}
return $the_object;
}
public function has_the_attribute($attribute) {
$object_properties = get_object_vars($this);
return array_key_exists($attribute, $object_properties);
}
You have to use PDO::errorInfo():
(...)
public function query($sql, $params = []) {
try {
$query = $this->connection->prepare($sql);
if( !$query )
{
$error = $this->connection->errorInfo();
die( "mysql error: {$error[2]}" );
}
} catch (Exception $e) {
var_dump('mysql error: ' . $e->getMessage());
}
(...)
}
PDO::errorInfo returns an array:
Element 0: SQLSTATE error code (a five characters alphanumeric identifier defined in the ANSI SQL standard);
Element 1: Driver-specific error code;
Element 2: Driver-specific error message.
I'm tinkering with a class that 'should' allow me to easily execute a fetchall query and display the results within a foreach statement. I assume all is working correctly as I have no errors. As for the foreach - this must be the problem? How would I foreach the results gained from the $connect->query()? I'm new to using any database OOP framework in my functions so I could be along the wrong lines completely.
<?
error_reporting(1);
class dbconnect {
private $host;
private $database;
private $username;
private $password;
private $pdo;
private $error;
public function __construct() {
$this->host = "localhost"; // Host
$this->database = "images"; // Database Name
$this->username = "*"; // Username
$this->password = "*"; // Password
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
);
try {
$this->pdo = new PDO("mysql:host={$this->host};dbname={$this->dbname};charset=utf8", $this->username, $this->password, $options);
}
catch(PDOException $e) {
$this->error = $e->getMessage();
}
}
public function query($query) {
try {
$stmt = $this->pdo->prepare($query);
$stmt->execute();
} catch(PDOException $ex) {
die("Failed to run query: " . $ex->getMessage());
}
$rows = $stmt->fetchAll();
return $rows;
}
}
$connect = new dbconnect;
$rows = $connect->query("select * from photos");
foreach($rows as $row):
print $row['id'];
endforeach;
?>
The $rows variable you're declaring inside query is not accessible to the outside, it is local to that function. Most likely, you simply want to return those results to the caller:
$rows = $stmt->fetchAll();
return $rows; // return value from function...
and have the caller capture that return value in its own variable:
$rows = $connect->query("select * from images"); // ... is received by caller
foreach($rows as $row):
Also check out dougjore's answer, you're mixing $this->stmt and $stmt inside your query method.
Pretty sure you aren't ever actually executing the query:
$this->stmt = $this->pdo->prepare($query);
$stmt->execute();
I believe (I could be wrong, I'm rather new to PDO myself and I haven't built a class for it), that you need to say $this->stmt->execute();
You could do
//PDO::FETCH_ASSOC: returns an array indexed by column name as returned in your result set
$this->stmt = $this->pdo->prepare($query);
$this->stmt->execute();
while ($result = $this->stmt->fetch(PDO::FETCH_ASSOC))
{
//do something with the result
}
Have a look here for more options to fetch PDO query results:
http://php.net/manual/en/pdostatement.fetch.php
$connect = new dbconnect;
$sql="select * from photos";
$stmt=$connect->pdo->prepare($sql);
$stmt->execute();
$result=$stmt->fetch(PDO::FETCH_ASSOC);
foreach($result as $key => $value) {
echo $key . "-" . $value . "<br/>";
}
I've tried disabling emulated prepares in PDO but I cannot get it to work. Everything else works. The query is successful. The reason I believe it's not working is because it does not escape quotes and such so I get syntax errors.
I've tried doing it two different ways.
$this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
and
$insert = $database->$con->prepare($insert, array(PDO::ATTR_EMULATE_PREPARES => false));
I've also noticed that getAttribute does not work.
By doing this...
$emul = $database->$con->getAttribute(PDO::ATTR_EMULATE_PREPARES);
var_dump($emul);
...I get this error
SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute
And here's my database class where the action happens. (I might have left some unneccessary/stupid code in there while I was testing.)
<?php
class Database
{
public $dbh;
public $dbh1;
public $dbh2;
private static $instance;
public $numResults;
private $result = array(); // Results that are returned from the query
public function __construct()
{
try
{
$this->dbh = new PDO(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8', DB_USER, DB_PASS);
$this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->dbh1 = new PDO(DB_TYPE1.':host='.DB_HOST1.';dbname='.DB_NAME1.';charset=utf8', DB_USER1, DB_PASS1);
$this->dbh1->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->dbh1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->dbh2 = new PDO(DB_TYPE2.':host='.DB_HOST2.';dbname='.DB_NAME2.';charset=utf8', DB_USER2, DB_PASS2);
$this->dbh2->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->dbh2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch (PDOException $e)
{
die("Database Error: ". $e->getMessage() . "<br />");
}
}
public static function getInstance()
{
if (!isset(self::$instance))
{
$object = __CLASS__;
self::$instance = new $object;
}
return self::$instance;
}
private function tableExists($table, $con)
{
switch($con)
{
case 'dbh':
$db_name = DB_NAME;
break;
case 'dbh1':
$db_name = DB_NAME1;
break;
case 'dbh2':
$db_name = DB_NAME2;
break;
}
$database = Database::getInstance();
if(is_array($table))
{
for($i = 0; $i < count($table); $i++)
{
$tablesInDb = $database->$con->prepare('SHOW TABLES FROM '.$db_name.' LIKE "'.$table[$i].'"');
$tablesInDb->execute();
$rowCount = $tablesInDb->rowCount();
if($tablesInDb)
{
if($rowCount <> 1)
{
die('Error: Table does not exist'.$table[$i]);
}
}
}
}else
{
$tablesInDb = $database->$con->prepare('SHOW TABLES FROM '.$db_name.' LIKE "'.$table.'"');
$tablesInDb->execute();
$rowCount = $tablesInDb->rowCount();
if($tablesInDb)
{
if($rowCount <> 1)
{
die('Error: Table does not exist'.$table);
}
}
}
return true;
}
public function insert($con, $table, $values, $cols = null)
{
if($this->tableExists($table, $con))
{
$insert = 'INSERT INTO '.$table;
if($cols != null)
{
$cols = implode(',', $cols);
$insert.= '('.$cols.')';
}
for($i = 0; $i < count($values); $i++)
{
if(is_string($values[$i]))
$values[$i] = "'".$values[$i]."'";
}
$values = implode(',', $values);
$insert .= ' VALUES ('.$values.')';
$database = Database::getInstance();
$insert = $database->$con->prepare($insert, array(PDO::ATTR_EMULATE_PREPARES => false));
$insert->execute();
if($insert)
{
return true;
}else
{
return false;
}
}
}
public function getResult()
{
return $this->result;
}
}
?>
As manual states, getAttribute() don't support ATTR_EMULATE_PREPARES
There shouldn't be no escaping with native prepares at all.
To check if you are in emulation mode or not you can use LIMIT clause with lazy binding. It will raise an error if emulation is on.
Your main problem is whatever "syntax error" you mentioned and you have to solve it first.
As Álvaro G. Vicario noted in comments, you are not using prepared statements. It is apparently the root of the problem. PDO doesn't "escape" your data by itself. It can do it only if you are using placeholders to represent your data in the query. You can read more here
I want to know how to use named parameters in a prepared statement with pdo class, so the call to pdo look something like following.
$query = $bdd->prepare('SELECT * FROM table WHERE login = :login AND pww = :pww');
$query->execute(array('login' => $login, 'pww' => $pww));
And I want to integrate this on a class regardless of the number of parameters.
Currently, I have this code
require_once 'constants.php';
class Mysql extends PDO {
private $con;
public function __construct() {
try {
$this->con = parent::__construct(DB_DSN, DB_USER, DB_PASS);
if ($this->getAttribute(PDO::ATTR_DRIVER_NAME) == DB_TYPE)
$this->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, TRUE);
return $this->con;
} catch (PDOException $e) {
die('Error:' . $e->getMessage());
}
}
public function select($reqSelect) {
try {
$this->con = parent::beginTransaction();
$result = parent::prepare($reqSelect);
$result->execute();
//$this->con = parent::commit();
$this->con = parent::rollBack();
return $result;
$result->closeCursor();
} catch (Exception $e) {
die('Error:' . $e->getMessage());
}
}
public function selectAll($reqSelect) {
$result = parent::prepare($reqSelect);
$result->execute();
$resultat = $result->fetchAll();
return $resultat;
$result->closeCursor();
}
}
And for parameters, I use somethings like ( which is wrong and vulnerable to injection )
require_once 'classes/Mysql.class.php';
$mysql = new Mysql();
$sql = 'SELECT * FROM articles WHERE id = '.$_GET['id'].' LIMIT 1';
$data = $mysql->select($sql);
Thanks.
So it's seems that I have figured it out, the trick was adding an optional parameter to the function, you use it whenever you need to work with prepared statements (named parameters).
So the function is something like
public function selectAll($reqSelect, $param = null) {
$result = parent::prepare($reqSelect);
//Check whether the parameter was passed or not
if (is_null($param)) {
$result->execute();
$resultat = $result->fetchAll();
return $resultat;
} else {
//Binding the parameters
$result->execute($param);
$resultat = $result->fetchAll();
return $resultat;
}
$result->closeCursor();
}
And for applying it, it goes like
//First param, the SQL. Here we have named parameters, so we need them to get bind
$sql = 'SELECT * FROM articles WHERE publish = :number';
//Second param, the parameters that will get bind with the named ones
$param = array(':number' => 1);
$query = $mysql->selectAll($sql, $param);
foreach ($query as $row) {
extract($row);
echo $title . '<br />';
}
I don't know if this, is considered the best practice, secured or even correct. if I'm mistaken feel free to correct me.