Giter VIP home page Giter VIP logo

easydb's Introduction

EasyDB - Simple Database Abstraction Layer

Build Status Latest Stable Version Latest Unstable Version License Downloads

PDO lacks brevity and simplicity; EasyDB makes separating data from instructions easy (and aesthetically pleasing).

EasyDB was created by Paragon Initiative Enterprises as part of our effort to encourage better application security practices.

Check out our other open source projects too.

If you're looking for a full-fledged query builder, check out Latitude and Aura.SqlQuery, which can be used with EasyDB.

If you'd like to use EasyDB but cache prepared statements in memory for multiple queries (i.e. to reduce database round-trips), check out our EasyDB-Cache wrapper class.

Installing EasyDB

First, get Composer, if you don't already use it.

Next, run the following command:

/path/to/your/local/composer.phar require paragonie/easydb:^3

If you've installed Composer in /usr/bin, you can replace /path/to/your/local/composer.phar with just composer.

Why Use EasyDB? Because it's cleaner!

Let's refactor a dangerous PHP snippet that previously used string concatenation to pass user input instead of prepared statements. For example, imagine something that just dropped {$_GET['blogpostid']} into the middle of a mysql_query() statement. Let's make it secure.

The PDO Way

$db = new \PDO(
    'mysql:host=localhost;dbname=something',
    'username',
    'putastrongpasswordhere'
);

$statement = $db->prepare('SELECT * FROM comments WHERE blogpostid = ? ORDER BY created ASC');
$exec = $statement->execute([$_GET['blogpostid']]);
$rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
foreach ($rows as $row) {
    $template_engine->render('comment', $row);
}

That's a little wordy for such a simple task. If we do this in multiple places, we end up repeating ourselves a lot.

The EasyDB Solution

$db = \ParagonIE\EasyDB\Factory::fromArray([
    'mysql:host=localhost;dbname=something',
    'username',
    'putastrongpasswordhere'
]);

$rows = $db->run('SELECT * FROM comments WHERE blogpostid = ? ORDER BY created ASC', $_GET['blogpostid']);
foreach ($rows as $row) {
    $template_engine->render('comment', $row);
}

We made it a one-liner.

What else can EasyDB do quickly?

Insert a row into a database table

$db->insert('comments', [
    'blogpostid' => $_POST['blogpost'],
    'userid' => $_SESSION['user'],
    'comment' => $_POST['body'],
    'parent' => isset($_POST['replyTo']) ? $_POST['replyTo'] : null
]);

This is equivalent to the following SQL query (assuming $_POST['blogpostid'] is equal to 123, $_SESSION['user'] is equal to 234, $_POST['body'] is equal to test, and $_POST['replyTo'] is equal to 3456):

INSERT INTO comments (blogpostid, userid, comment, parent) VALUES (
    123,
    234,
    'test',
    3456
);

Build an insert without executing

$sql = $db->buildInsertQuery('comments', [
    'blogpostid',
    'userid',
    'comment'
]);

// INSERT INTO comments (blogpostid, userid, comment) VALUES (?, ?, ?)

$result = $db->q(
    $sql,
    $values,
    \PDO::FETCH_BOTH,
    true
);

Update a row from a database table

$db->update('comments', [
    'column' => 'foo',
    'otherColumn' => 123456,
    'approved' => true
], [
    'commentid' => $_POST['comment']
]);

This is equivalent to the following SQL query (assuming $_POST['comment'] is equal to 789):

UPDATE comments
SET 
  column = 'foo',
  otherColumn = 123456,
  approved = TRUE
WHERE commentid = 789

Delete a row from a database table

// Delete all of this user's comments
$db->delete('comments', [
    'userid' => 3
]);

This is equivalent to the following SQL query:

DELETE FROM comments WHERE userid = 3

Fetch a single row from a table

$userData = $db->row(
    "SELECT * FROM users WHERE userid = ?",
    $_GET['userid']
);

Note: This expects a variadic list of arguments, not an array. If you have multiple parameters, stack them like this:

$userData = $db->row(
    "SELECT * FROM users WHERE userid = ? AND other = ?",
    $_GET['userid'],
    $_GET['other']
);

This is wrong:

$userData = $db->row(
    "SELECT * FROM users WHERE userid = ? AND other = ?",
    array($userid, $other) // WRONG, should not be in an array
);

Fetch a single column from a single row from a table

$exists = $db->cell(
    "SELECT count(id) FROM users WHERE email = ?",
    $_POST['email']
);

/* OR YOU CAN CALL IT THIS WAY: */
$exists = $db->single(
    "SELECT count(id) FROM users WHERE email = ?",
    array(
        $_POST['email']
    )
);

Note: cell() expects a variadic list of arguments, not an array. If you have multiple parameters, stack them like this:

$exists = $db->cell(
    "SELECT count(id) FROM users WHERE email = ? AND username = ?",
    $_POST['email'],
    $_POST['usenrame']
);

This is wrong:

$exists = $db->cell(
    "SELECT count(id) FROM users WHERE email = ? AND username = ?",
    array($email, $username) // WRONG, should not be in an array
);

Alternatively, you can use single() instead of cell() if you really want to pass an array.

Try to perform a transaction

$save = function (EasyDB $db) use ($userData, $query) : int {
    $db->safeQuery($query, [$userData['userId']]);
    return \Some\Other\Package::CleanUpTable($db);
};
// auto starts, commits and rolls back a transaction as necessary
$returnedInt = $db->tryFlatTransaction($save);

Generate dynamic query conditions

$statement = EasyStatement::open()
    ->with('last_login IS NOT NULL');

if (strpos($_POST['search'], '@') !== false) {
    // Perform a username search
    $statement->orWith('username LIKE ?', '%' . $db->escapeLikeValue($_POST['search']) . '%');
} else {
    // Perform an email search
    $statement->orWith('email = ?', $_POST['search']);
}

// The statement can compile itself to a string with placeholders:
echo $statement; /* last_login IS NOT NULL OR username LIKE ? */

// All the values passed to the statement are captured and can be used for querying:
$user = $db->single("SELECT * FROM users WHERE $statement", $statement->values());

Note: Passing values with conditions is entirely optional but recommended.

Variable number of "IN" arguments

// Statements also handle translation for IN conditions with variable arguments,
// using a special ?* placeholder:
$roles = [1];
if ($_GET['with_managers']) {
    $roles[] = 2;
}

$statement = EasyStatement::open()->in('role IN (?*)', $roles);

// The ?* placeholder is replaced by the correct number of ? placeholders:
echo $statement; /* role IN (?, ?) */

// And the values will be unpacked accordingly:
print_r($statement->values()); /* [1, 2] */

Grouping of conditions

// Statements can also be grouped when necessary:
$statement = EasyStatement::open()
    ->group()
        ->with('subtotal > ?')
        ->andWith('taxes > ?')
    ->end()
    ->orGroup()
        ->with('cost > ?')
        ->andWith('cancelled = 1')
    ->end();

echo $statement; /* (subtotal > ? AND taxes > ?) OR (cost > ? AND cancelled = 1) */

Insert and Update with custom placeholder

Since Version 2.12.0, EasyDB supports placeholders for calling stored procedures and SQL functions when inserting or updating data.

The EasyPlaceholder class is constructed in the same fashion as other EasyDB methods: The first argument, the "mask", must be a string. The mask may contain ? placeholders, and any subsequent arguments will fill in for the ? placeholders when the query is executed.

$db->insert('user_auth', [
    'user_id' => 1,
    'timestamp' => new EasyPlaceholder('NOW()'),
    'expired' => new EasyPlaceholder('TIMESTAMPADD(HOUR, 2, NOW())'),
    'location' => new EasyPlaceholder(
        "ST_GeomFromText(CONCAT('POINT(', ?, ' ', ?, ')'))",
        50.4019514,
        30.3926105
    )
]);

$db->update(
    'user_auth', 
    [
        'last_update' => new EasyPlaceholder('NOW()'),
    ], 
    [
        'user_id' => 1
    ]
);

Security warning: Do not concatenate user input into the first parameter.

EasyPlaceholder can be used in insert(), insertIgnore(), insertOnDuplicateKeyUpdate(), and update().

What if I need PDO for something specific?

$pdo = $db->getPdo();

Can I create an EasyDB wrapper for an existing PDO instance?

Yes! It's as simple as doing this:

$easy = new \ParagonIE\EasyDB\EasyDB($pdo, 'mysql');

How do I run tests ?

vendor/bin/phpunit

Using Psalm's Security Analysis with EasyDB

First, make sure you've read the Psalm documentation.

EasyDB's API exposes several taint sinks. Next, run the following command on your codebase that uses EasyDB to identify sources of SQL injection risk.

vendor/bin/psalm --taint-analysis

This will expose where you're passing tainted data to EasyDB in a potentially unsafe way.

Troubleshooting Common Issues

Only one-dimensional arrays are allowed

This comes up a lot when trying to pass an array of parameters to run().

EasyDB::run() expects a query string, then any number of optional parameters. It does NOT expect an array of all the parameters.

If you want to use an API that looks like $obj->method($string, $array), use safeQuery() instead of run().

<?php
/**
 * @var EasyDB $db
 * @var string $query
 * @var array $params 
 */
- $rows = $db->run($query, $params);
+ $rows = $db->safeQuery($query, $params);

Alternatively, you can flatten your array with the splat operator:

<?php
/**
 * @var EasyDB $db
 * @var string $query
 * @var array $params 
 */
- $rows = $db->run($query, $params);
+ $rows = $db->run($query, ...$params);

EasyDB's run() method is a variadic wrapper for safeQuery(), so either solution is correct.

Support Contracts

If your company uses this library in their products or services, you may be interested in purchasing a support contract from Paragon Initiative Enterprises.

easydb's People

Contributors

alofoxx avatar aracoool avatar chengxuncc avatar colshrapnel avatar edueo avatar gavinggordon avatar inisik avatar kamil-tekiela avatar lesuisse avatar mignaulo avatar nenglish7 avatar nfreader avatar ntzm avatar paragonie-scott avatar paragonie-security avatar ruslanmelnychenko avatar shadowhand avatar signpostmarv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

easydb's Issues

Impossible to use update method with EasyStatement

Can you add an opportunity to use EasyStatement with the update method?
For example:

$db->update(
    'database_table',
    ['event_status' => self::EVENT_STATUS_PROCESSING],
    EasyStatement::open()->in('event_id IN (?*)', $ids)
);

Column Name

Are there any security risks if the form field name and mysql database column names are identical ?

Sanitation / input validation

In your example you used $_GET to make a SQL statement, Does this mean that you also perform input sanitation?
"SELECT * FROM comments WHERE blogpostid = {$_GET['blogpostid']} ORDER BY created ASC"

EasyStatement: Support Joins

Before I release v2.1.0 and v1.1.0 I intend to add support for JOINs:

  • JOIN
  • LEFT JOIN

I'm tackling this tonight.

Fetch mode is always FETCH_ASSOC

Given the following setup:

$options = [
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ
];
$db = \ParagonIE\EasyDB\Factory::create(
    $dsn,
    $username,
    $password
    $options
);

...we should respect the configured setting in a backwards-compatible way. (i.e. if explicitly passed, use that, otherwise, default to FETCH_ASSOC like we always do).

Empty EasyStatement clauses cause problems

Empty EasyStatements should stringify as "1", not an empty string. Consider:

$sth1 = \ParagonIE\EasyDB\EasyStatement::open();

$sth2 = \ParagonIE\EasyDB\EasyStatement::open();
$sth2->with("a=a");
$sth2->with($sth1);

print $sth2;
// -> a=a OR

Installation / use?

Hi,
can you maybe add a short installation how-to for people not using Composer? Not everyone has his/her own server, I'm using shared hosting.
Thanks.

How to run trigger queries

how can you run trigger queries. I get the following error.
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.
I have tried with and without using delimiter statements and it still did not work.

   $triggerquery = " CREATE TRIGGER `$triggername` $triggertime ON `$tablename` 
							FOR EACH ROW 
							BEGIN 
								IF(NEW.`$triggercol` IS NULL) 
								THEN 
									SET NEW.`$triggercol` = UNIX_TIMESTAMP(); 
								END IF; 
							END;
						";

Proposal to remove the redefining of PDOExceptions

I had an issue today that was being masked by an incorrect error message. Had the library have returned a PDOException from the Factory class I would have been able to solve the issue much quicker.

Can I propose the remove of the redefining of PDOException's? I see no purpose in them as the PDOExceptions are already clear and readable.

At the very least return $e->getMessage() because the following catchall is wrong:

 'Could not create a PDO connection. Please check your username and password.'

You could completely remove that try catch block and allow the user to try/catch PDOException because at present it's just rethrowing?

Referring to this block: https://github.com/paragonie/easydb/blob/master/src/Factory.php#L86

EasyStatement throws exception when the array is empty

Sometimes there is a need to build complex WHERE clauses and building the EasyStatement conditionally is not very convenient. If the values array is null EasyStatement should not add that WHERE clause to the SQL instead of letting PHP dump about incorrect number of arguments.

Example:

$dynamicArray = []; // Values are dynamically build somewhere else, but the array could also be empty
EasyStatement::open()->in('`Id` IN (?*)', $dynamicArray);

The result of this will be:

Warning: str_repeat(): Second argument has to be greater than or equal to 0 in ...EasyStatement.php on line 380

To avoid the exception I need to check if the array is empty before adding the next clause to EasyStatement

UTF8 vs utf8mb4

is there any greater vulnerability to using utf8mb4 than utf8? Also will unicode automatically map to utf8mb4 for mysql / percona / mariadb?

How well does this work with other DBMS?

How to run CREATE TABLE queries? (run and query methods fail)

I have the following setup:

$db = Factory::create('sqlite:memory.db');
$db->run($sql);
// respectively
$db->query($sql);

The following CREATE TABLE query:

CREATE TABLE IF NOT EXISTS predicate (
    id      INTEGER PRIMARY KEY AUTOINCREMENT,
    value   TEXT,
    UNIQUE(value)
);

fails to run using run and query method.

run method

Error:

TypeError: Argument 1 passed to ParagonIE\EasyDB\EasyDB::getResultsStrictTyped() 
must be an instance of PDOStatement, boolean given, called in 
.../vendor/paragonie/easydb/src/EasyDB.php on line 759

.../vendor/paragonie/easydb/src/EasyDB.php:1042
...vendor/paragonie/easydb/src/EasyDB.php:759
.../vendor/paragonie/easydb/src/EasyDB.php:722
.../Adapter/SQliteMemory/Store.php:81
.../Adapter/SQliteMemory/StoreTest.php:14

query method

TypeError: Return value of ParagonIE\EasyDB\EasyDB::query() must be an instance of 
PDOStatement, boolean returned

.../vendor/paragonie/easydb/src/EasyDB.php:1197
.../src/beardfulldb/Adapter/SQliteMemory/Store.php:81

What did i wrong?

query re: paragonie/corner

If one were to tinker with the exceptions thrown by easydb, would it be better to change ParagonIE\EasyDB\Exception\ExceptionInterface to extend from ParagonIE\Corner\CornerInterface, or extend the exception classes & implement CornerInterface there ?

EasyStatement uses incorrect precedence when nested

$sth1 = \ParagonIE\EasyDB\EasyStatement::open();
$sth1->with("a=1");
$sth1->orWith("a=2");
$sth1->orWith("a=3");

$sth2 = \ParagonIE\EasyDB\EasyStatement::open();
$sth2->with("status=1");
$sth2->andWith($sth1);

print $sth2;
// -> status=1 AND a=1 OR a=2 OR a=3

In MySQL, AND has a higher precedence than OR, so this clause is parsed as

(status=1 AND a=1) OR (a=2) OR (a=3)

Passing an EasyStatement as an argument to another statement should wrap it in parentheses.

How to run a statement/query without parameters?

I have this query:
SELECT MAX(fieldName) AS maxValue FROM myTable;

I'll try to fetch the single column:
$maxValue = $db->cell( "SELECT MAX(fieldName) AS maxValue FROM myTable;",null);

The public function single(... returns the error HY093: Invalid parameter number: parameter was not defined.

How to run a PDO:statement without parameters?
Thanks in advance, Francesco

Removed phpcs from Travis CI checks

2fde3d8

It erroneously believes that there is no code coverage for Factory:;create() despite an explicit unit test being created for Factory::create().

I don't care to dive into the idiosyncrasies of this library. I've removed it from the Travis CI checks. It's wasting my time with a false build failure.

Why not use PDO::ATTR_DRIVER_NAME?

Currently in order to enable the correct quoting string, we need to pass $dbEngine. It looks like this code could be replaced with:

if (empty($dbEngine)) {
  $dbEngine = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
}

Any reason not to do this?

Difficult to use Postgres RETURNING clause

Current it is not possible to use insert() or insertGet() to return the last inserted id. For instance, with Postgres we can return the inserted PK but since the insert() method forces the return value be a single (int), a custom insert has to be executed instead.

Ideally, it would be possible to do something like this:

$result = $db->insertReturning($table, $values, $column);

// as per the PHP docs example:
// INSERT INTO employee(user_id, name) VALUES(?, ?) RETURNING employee_id

$values = ['user_id' => 5, 'name' => 'John Smith'];
$id = $db->insertReturning('employee', $values, 'employee_id');

Alternatively, this can be abstracted as insertGetId() since some databases (MySQL, for example) will return the correct thing from PDO::lastInsertId().

Multiple IN's

Would like to be able to use multiple IN's in a WHERE statement without errors about only one dimensional arrays being allowed.

$states[] = (IL, IN, VA);
run("SELECT id FROM states WHERE state IN (?)", $states);

The only way to achieve this is to do the following, which leaves one open to injection attacks, right?

run("SELECT id FROM states WHERE state IN (" . implode(',', $states) . ")");

php 5 support

Is there any legacy support for PHP 5 with easydb? I noticed right away there were some PHP 7 features in the source code. I need a good PDO wrapper for an older project.

Multiple issues with EasyDB::insertGet

The documentation for this function doesn't clearly explain its purpose. As best I can tell, it's intended to insert a row, then return its auto-increment ID. (Is this accurate?)

The straightforward way of doing this would be to use PDO::lastInsertId(), which is already exposed as EasyDB::lastInsertId(). However, insertGet() instead takes the roundabout approach of performing a follow-up SELECT using every column in the inserted data as a condition. This is likely to result in strange query plans, especially in tables with many indices. Under some contrived circumstances (e.g, a table with an index covering a low-cardinality column and a monotonically decreasing column), the resulting plan may end up scanning a large portion of the table before finding the intended row.

If insertGet() is called with a $map which matches data which already exists in the table and $field is not the auto-increment column, the value returned by insertGet() may correspond to a different row than the one which was most recently inserted. (Under the mysql and pgsql drivers, it will be the largest value which exists in a matching row; under other drivers, it will be an arbitrary value.)

If $map is empty, the SELECT will fail entirely, as the WHERE clause is empty.

Error Management

if i try to get a row that doesnt exist, follow error will show on page:
Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'name' in 'order clause' in /var/www/

how can i catch that error so that the visitor can't see this error, and i can show own error message and log this easydb error to my own logfile ?

Version 2.4.0

I'm going to give EasyDB a very quick code review. Once this is closed, consider the review completed.

Once this is done, at your earliest convenience, @shadowhand, can you convert and create the PHP 5.x release? I'll tag/sign/release 2.4.0 shortly thereafter.

How to get affected rows?

In safeQuery() the prepared statement exists only in the local scope, thus all high-level methods like insert, update, delete do not provide any means to obtains the affected rows. Or did I get something wrong?

Connection Issue

First of all, I really like how easydb is coded, it's easy and brief than using the actually PDO stuff!

I downloaded easydb with composer, using paragonie/easydb v2.2.1.

I'm just having a weird odd issue right now with easydb connecting, I'm not sure where the issue can be, I checked the SQL server, it appears to be online and running. Using MariaDB 5.5.52.

This is what I used to connect via easydb...

$db = \ParagonIE\EasyDB\Factory::create( 'mysql;host=localhost;dbname=databasename', 'databaseuser', 'databasepw' );

And this is the error it outputted...

Fatal error: Uncaught ParagonIE\EasyDB\Exception\ConstructorFailed: Could not create a PDO connection. Please check your username and password. in /var/www/vhosts/phoenix/httpdocs/area52/system_int/composer/vendor/paragonie/easydb/src/Factory.php:56 Stack trace: #0 /var/www/vhosts/phoenix/httpdocs/area52/system_int/bootstrap.inc.php(67): ParagonIE\EasyDB\Factory::create('mysql;host=loca...', 'databaseuser', 'databasepw') #1 /var/www/vhosts/phoenix/httpdocs/area52/index.php(3): require('/var/www/vhosts...') #2 {main} thrown in /var/www/vhosts/phoenix/httpdocs/area52/system_int/composer/vendor/paragonie/easydb/src/Factory.php on line 56

So I thought it was a error in my credentials, I checked and they were correct, however this wasn't the case after I used straight PDO from PHP and it worked by using this...

$db = new PDO('mysql:host=localhost;dbname=databasename;charset=utf8', 'databaseuser', 'databasepw');

I checked the easydb source code, and I can't figure out what can be the issue. I'm hoping you can help me out here? My PHP version is 7.1.

Thanks!

Possible credentials leak

I was reached by a user concerned of the possible leak of the database credentials when an error occurs during Factory:create() call:

PHP Fatal error: Uncaught ParagonIE\EasyDB\Exception\ConstructorFailed: Could not create a PDO connection. Please check your username and password. in vendor/paragonie/easydb/src/Factory.php:77
Stack trace:
#0 0.php(10): ParagonIE\EasyDB\Factory::create('mysql:host=loca...', 'username', 'putastrongpassw...')
#1 {main}
thrown in vendor/paragonie/easydb/src/Factory.php on line 77

The problem is coming from the fact that Factory::create()'s parameters are listed in the stack trace.

I offered a user a quick and dirty solution of wrapping the call into a try catch and then re-throwing a generic exception that contains the error message from the caught exception.

But that's only a workaround and I think it would be better to change the Factory::create() method's signature. the simplest solution would be to make the method to accept an array of parameters instead of an explicit list of variables. This is against the best practices but here I would think it would be a good tradeoff between good practices and security.

I could send a pull request if you agree for this change.
Or we can try to find some other solution.

select in(...)

What protections does this offer for SELECT blah FROM blah WHERE blah IN($injected)

type queries

Composer version Installation

When I use composer require paragonie/easydb:^2, it installs 2.0.0.
2.0.0 does not allow me to connect to my database. But, after forcing 2.8.0, with the same code I am able to connect to my database.

Using MySQL functions in insert() or update()

Do you have thoughts on how this wrapper could be modified to safely allow MySQL functions as parameters when using insert() or update()? These two shortcuts are tremendously useful but not being able to use them with native MySQL functions restricts their use in some areas.

As an example, we have queries that make use of NOW() which would not use any parameter marker. We also have queries that make use of NET_ATON(ip) which would still use a parameter marker ?

tests?

Cannot see any obvious unit testing, how reliable is this?

Suggestion, return an named array

This seems like a great library!

What I feel could easily be added would be a function that delivers an ready array, Something to replace

$rows = $easydb->run();
foreach ($rows as $row) {
    $arr[$row[$keyfield]] = $row[$valuefield];
}

As a second suggestion, an array with a row key and all the other fields as rows in the array, that would replace

$rows = $easydb->run();
foreach ($rows as $row) {
    $arr[$row[$keyfield]] = $row;
}

Odd issue with 1D Array errors

So if I run this query...

$db->run('SELECT * FROM users WHERE userid=? AND first_name=?', [1, 'Kyle'])

I get this message. As this is clear to me that I'm only passing a 1D array, but I'm not sure why this error keeps popping out.

Fatal error: Uncaught InvalidArgumentException: Only one-dimensional arrays are allowed.
File: EasyDB.php:651

Any ideas?

Connection issues

I can connect to my database using straight PDO. But, cannot connect using easydb. It gives PDO connection error. Check username & password. I am using V2.0.0 with Php 7.2. In addition, the error displays username and password and would like to suppress those.

Thows 'Only one-dimensional arrays are allowed' usin 1D array

$rows = $db->run('SELECT ?', 1); // works
$rows = $db->run('SELECT ?', [1]); // Error

How to run select query with multiple bind variables?
for example:

SELECT
    id,
    username AS "nam",
    email AS "user_email",
    COUNT(*) AS "some_count"
FROM
    "users"
WHERE
    username = :_1_
    AND surname = :_2_
GROUP BY
    id,
    email

503 Service Unavailable

When I try to connect to the database through EasyDB I get an error 503 Unavailable service.
Through PDO everything is fine.

require_once "vendor/autoload.php";
$DB = \ParagonIE\EasyDB\Factory::create(
    'mysql:host=localhost;dbname=testing',
    'testing',
    'HN8yu1s1'
);

PHP7.1 cgi

rfc, flat transaction method ?

A pattern I'm using a lot at work is something like this:

$db = $cms->getDatabaseConnection();
$autoStartTransaction = ($db->inTransaction() === false);
if ($autoStartTransaction) {
    $db->beginTransaction();
}
try {
/**
Do thing here
*/
    if ($autoStartTransaction) {
        $db->commit();
    }
} catch (Throwable $e) {
    if ($autoStartTransaction) {
        $db->rollBack();
    }
    throw $e;
}

I'm wondering if there'd be a desirable utility in having a method with a signature something like this:

public function TryFlatTransaction(callable $cb)
{
    $autoStartTransaction = ($db->inTransaction() === false);
    if ($autoStartTransaction) {
        $db->beginTransaction();
    }
    try {
        $cb($this);
        if ($autoStartTransaction) {
            $db->commit();
        }
    } catch (Throwable $e) {
        if ($autoStartTransaction) {
            $db->rollBack();
        }
        throw $e;
    }
}

Where usage is something like this:

class Foo
{
    private $db;
    function __construct(EasyDB $db) {
        $this->db = $db;
    }

    function doThing($input) {
        $this->db->TryFlatTransaction(function (EasyDB $db) use ($input) {
            $db->safeQuery(/* some query */, /* some args from $input*/); // might throw an exception
            /**
            do something else that might throw an exception
            */
        });
    }

    function doRecursiveThing($input) {
        foreach ($input as $maybeRecursive) {
            $this->db->TryFlatTransaction(function (EasyDB $db) use ($maybeRecursive) {
                $this->doThing($maybeRecursive);
                if (
                    // some recursive check
                ) {
                    $this->doRecursiveThing($maybeRecursive);
                }
            });
        }
    }
}

Allow more characters in field names

I tried to use EasyDB with an in-memory sqlite DB with special field names in the table. EasyDB doesn't accept them. I changed allowSeperators to true but than it filters characters from my fieldlist and doesn't find it anymore. SQlite itself doesn't have problems with this.

To reproduce, try this:

$db = \ParagonIE\EasyDB\Factory::create('sqlite::memory:');
php > $db->setAllowSeparators(true);
php > $db->run('create table test(`a/b` TEXT);'); // works
php > $db->insert('test', [ 'a/b' => "test" ]); // exception thrown

Causes:

Warning: Uncaught PDOException: SQLSTATE[HY000]: General error: 1 table test has no column named ab in /usr/src/app/vendor/paragonie/easydb/src/EasyDB.php:641
Stack trace:
#0 /usr/src/app/vendor/paragonie/easydb/src/EasyDB.php(641): PDO->prepare('INSERT INTO "te...')
#1 /usr/src/app/vendor/paragonie/easydb/src/EasyDB.php(434): ParagonIE\EasyDB\EasyDB->safeQuery('INSERT INTO "te...', Array, 4, true)
#2 php shell code(1): ParagonIE\EasyDB\EasyDB->insert('test', Array)
#3 {main}
  thrown in /usr/src/app/vendor/paragonie/easydb/src/EasyDB.php on line 641

Informix not escape column and table name

I'm working with an Informix DB engine and I discovered that, in the CRUD operations, Informix generates an error if the colums and table names are escaped. I have updated the function escapeIdentifier(...) as shown below:

public function escapeIdentifier(string $string, bool $quote = true): string
{
....
 if ($quote) {
            switch ($this->dbEngine) {
                case 'mssql':
                    return '[' . $str . ']';
                case 'mysql':
                    return '`' . $str . '`';
                case 'informix':
                    return $str;                   
                default:
                    return '"' . $str . '"';
            }
        }
        return $str;
}

The solution it is under test and it seems work perfectly!

Best regards
Francesco

Version 2.0.0

I should be releasing version 2 before tomorrow. If anyone would care to give me feedback on the changes I've made this evening, please do.

Using LOAD DATA LOCAL INFILE with easydb

So, I have a text file with over 6 million lines of data, I have no options to use insert() as this leaks my server PHP memory as this takes almost 15 mins to import 6 millions rows. So I had to use the run() function to import the text file with load infile sql function but it tells me this...

General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll() or setting PDO::MYSQL_ATTR_USE_BUFFERED_QUERY

Which I thought the run() function executes fetchAll() which it does, so I tried setting the buffer query with setAttribute() and it didn't work, same error, which left me another option to try...

PDO::ATTR_EMULATE_PREPARES => true

Which I used to setAttribute(), and it tells me it doesn't allow the use of emulated prepared statements, which would be a security downgrade.

How can I use LOAD DATA LOCAL INFILE with easydb?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.