Giter Site home page Giter Site logo

dibi's Introduction

Dibi - smart database layer for PHP Buy me a coffee

Downloads this Month Tests Build Status Windows Latest Stable Version License

Introduction

Database access functions in PHP are not standardised. This library hides the differences between them, and above all, it gives you a very handy interface.

Support Me

Do you like Dibi? Are you looking forward to the new features?

Buy me a coffee

Thank you!

Installation

Install Dibi via Composer:

composer require dibi/dibi

The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.3.

Usage

Refer to the examples directory for examples. Dibi documentation is available on the homepage.

Connecting to database

The database connection is represented by the object Dibi\Connection:

$database = new Dibi\Connection([
	'driver'   => 'mysqli',
	'host'     => 'localhost',
	'username' => 'root',
	'password' => '***',
	'database' => 'table',
]);

$result = $database->query('SELECT * FROM users');

Alternatively, you can use the dibi static register, which maintains a connection object in a globally available storage and calls all the functions above it:

dibi::connect([
	'driver'   => 'mysqli',
	'host'     => 'localhost',
	'username' => 'root',
	'password' => '***',
	'database' => 'test',
	'charset'  => 'utf8',
]);

$result = dibi::query('SELECT * FROM users');

In the event of a connection error, it throws Dibi\Exception.

Queries

We query the database queries by the method query() which returns Dibi\Result. Rows are objects Dibi\Row.

You can try all the examples online at the playground.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

// array of all rows
$all = $result->fetchAll();

// array of all rows, key is 'id'
$all = $result->fetchAssoc('id');

// associative pairs id => name
$pairs = $result->fetchPairs('id', 'name');

// the number of rows of the result, if known, or number of affected rows
$count = $result->getRowCount();

Method fetchAssoc() can return a more complex associative array.

You can easily add parameters to the query, note the question mark:

$result = $database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active);

// or
$result = $database->query('SELECT * FROM users WHERE name = ?', $name, 'AND active = ?', $active););

$ids = [10, 20, 30];
$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids);

WARNING: Never concatenate parameters to SQL. It would create a SQL injection vulnerability.

$result = $database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!!

Instead of a question mark, so-called modifiers can be used.

$result = $database->query('SELECT * FROM users WHERE name = %s', $name);

In case of failure query() throws Dibi\Exception, or one of the descendants:

  • ConstraintViolationException - violation of a table constraint
  • ForeignKeyConstraintViolationException - invalid foreign key
  • NotNullConstraintViolationException - violation of the NOT NULL condition
  • UniqueConstraintViolationException - collides unique index

You can use also shortcuts:

// returns associative pairs id => name, shortcut for query(...)->fetchPairs()
$pairs = $database->fetchPairs('SELECT id, name FROM users');

// returns array of all rows, shortcut for query(...)->fetchAll()
$rows = $database->fetchAll('SELECT * FROM users');

// returns row, shortcut for query(...)->fetch()
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);

// returns field, shortcut for query(...)->fetchSingle()
$name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id);

Modifiers

In addition to the ? wildcard char, we can also use modifiers:

modifier description
%s string
%sN string, but '' translates as NULL
%bin binary data
%b boolean
%i integer
%iN integer, but 0 is translates as NULL
%f float
%d date (accepts DateTime, string or UNIX timestamp)
%dt datetime (accepts DateTime, string or UNIX timestamp)
%n identifier, ie the name of the table or column
%N identifier, treats period as a common character, ie alias or a database name (%n AS %N or DROP DATABASE %N)
%SQL SQL - directly inserts into SQL (the alternative is Dibi\Literal)
%ex SQL expression or array of expressions
%lmt special - adds LIMIT to the query
%ofs special - adds OFFSET to the query

Example:

$result = $database->query('SELECT * FROM users WHERE name = %s', $name);

If $name is null, the NULL is inserted into the SQL statement.

If the variable is an array, the modifier is applied to all of its elements and they are inserted into SQL separated by commas:

$ids = [10, '20', 30];
$result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids);
// SELECT * FROM users WHERE id IN (10, 20, 30)

The modifier %n is used if the table or column name is a variable. (Beware, do not allow the user to manipulate the content of such a variable):

$table = 'blog.users';
$column = 'name';
$result = $database->query('SELECT * FROM %n WHERE %n = ?', $table, $column, $value);
// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim'

Three special modifiers are available for LIKE:

modifier description
%like~ the expression starts with a string
%~like the expression ends with a string
%~like~ the expression contains a string
%like the expression matches a string

Search for names beginning with a string:

$result = $database->query('SELECT * FROM table WHERE name LIKE %like~', $query);

Modifiers for arrays

The parameter entered in the SQL query can also be an array. These modifiers determine how to compile the SQL statement:

modifier result
%and key1 = value1 AND key2 = value2 AND ...
%or key1 = value1 OR key2 = value2 OR ...
%a assoc key1 = value1, key2 = value2, ...
%l %in list (val1, val2, ...)
%v values (key1, key2, ...) VALUES (value1, value2, ...)
%m multi (key1, key2, ...) VALUES (value1, value2, ...), (value1, value2, ...), ...
%by ordering key1 ASC, key2 DESC ...
%n names key1, key2 AS alias, ...

Example:

$arr = [
	'a' => 'hello',
	'b'  => true,
];

$database->query('INSERT INTO table %v', $arr);
// INSERT INTO `table` (`a`, `b`) VALUES ('hello', 1)

$database->query('UPDATE `table` SET %a', $arr);
// UPDATE `table` SET `a`='hello', `b`=1

In the WHERE clause modifiers %and nebo %or can be used:

$result = $database->query('SELECT * FROM users WHERE %and', [
	'name' => $name,
	'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978

The modifier %by is used to sort, the keys show the columns, and the boolean value will determine whether to sort in ascending order:

$result = $database->query('SELECT id FROM author ORDER BY %by', [
	'id' => true, // ascending
	'name' => false, // descending
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

Insert, Update & Delete

We insert the data into an SQL query as an associative array. Modifiers and wildcards ? are not required in these cases.

$database->query('INSERT INTO users', [
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)

$id = $database->getInsertId(); // returns the auto-increment of the inserted record

$id = $database->getInsertId($sequence); // or sequence value

Multiple INSERT:

$database->query('INSERT INTO users', [
	'name' => 'Jim',
	'year' => 1978,
], [
	'name' => 'Jack',
	'year' => 1987,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)

Deleting:

$database->query('DELETE FROM users WHERE id = ?', $id);

// returns the number of deleted rows
$affectedRows = $database->getAffectedRows();

Update:

$database->query('UPDATE users SET', [
	'name' => $name,
	'year' => $year,
], 'WHERE id = ?', $id);
// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123

// returns the number of updated rows
$affectedRows = $database->getAffectedRows();

Insert an entry or update if it already exists:

$database->query('INSERT INTO users', [
	'id' => $id,
	'name' => $name,
	'year' => $year,
], 'ON DUPLICATE KEY UPDATE %a', [ // here the modifier %a must be used
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//   ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978

Transaction

There are three methods for dealing with transactions:

$database->begin();

$database->commit();

$database->rollback();

Testing

In order to play with Dibi a little, there is a test() method that you pass parameters like to query(), but instead of executing the SQL statement, it is echoed on the screen.

The query results can be echoed as a table using $result->dump().

These variables are also available:

dibi::$sql; // the latest SQL query
dibi::$elapsedTime; // its duration in sec
dibi::$numOfQueries;
dibi::$totalTime;

Complex queries

The parameter may also be an object DateTime.

$result = $database->query('SELECT * FROM users WHERE created < ?', new DateTime);

$database->query('INSERT INTO users', [
	'created' => new DateTime,
]);

Or SQL literal:

$database->query('UPDATE table SET', [
	'date' => $database->literal('NOW()'),
]);
// UPDATE table SET `date` = NOW()

Or an expression in which you can use ? or modifiers:

$database->query('UPDATE `table` SET', [
	'title' => $database::expression('SHA1(?)', 'secret'),
]);
// UPDATE `table` SET `title` = SHA1('secret')

When updating, modifiers can be placed directly in the keys:

$database->query('UPDATE table SET', [
	'date%SQL' => 'NOW()', // %SQL means SQL ;)
]);
// UPDATE table SET `date` = NOW()

In conditions (ie, for %and and %or modifiers), it is not necessary to specify the keys:

$result = $database->query('SELECT * FROM `table` WHERE %and', [
	'number > 10',
	'number < 100',
]);
// SELECT * FROM `table` WHERE (number > 10) AND (number < 100)

Modifiers or wildcards can also be used in expressions:

$result = $database->query('SELECT * FROM `table` WHERE %and', [
	['number > ?', 10],  // or $database::expression('number > ?', 10)
	['number < ?', 100],
	['%or', [
		'left' => 1,
		'top' => 2,
	]],
]);
// SELECT * FROM `table` WHERE (number > 10) AND (number < 100) AND (`left` = 1 OR `top` = 2)

The %ex modifier inserts all items of the array into SQL:

$result = $database->query('SELECT * FROM `table` WHERE %ex', [
	$database::expression('left = ?', 1),
	'AND',
	'top IS NULL',
]);
// SELECT * FROM `table` WHERE left = 1 AND top IS NULL

Conditions in the SQL

Conditional SQL commands are controlled by three modifiers %if, %else, and %end. The %if must be at the end of the string representing SQL and is followed by the variable:

$user = ???

$result = $database->query('
	SELECT *
	FROM table
	%if', isset($user), 'WHERE user=%s', $user, '%end
	ORDER BY name
');

The condition can be supplemented by the section %else:

$result = $database->query('
	SELECT *
	FROM %if', $cond, 'one_table %else second_table
');

Conditions can nest together.

Identifiers and strings in SQL

SQL itself goes through processing to meet the conventions of the database. The identifiers (names of tables and columns) can be entered into square brackets or backticks, strings are quoted with single or double quotation marks, but the server always sends what the database asks for. Example:

$database->query("UPDATE `table` SET [status]='I''m fine'");
// MySQL: UPDATE `table` SET `status`='I\'m fine'
// ODBC:  UPDATE [table] SET [status]='I''m fine'

The quotation marks are duplicated inside the string in SQL.

Result as associative array

Example: returns results as an associative field, where the key will be the value of the id field:

$assoc = $result->fetchAssoc('id');

The greatest power of fetchAssoc() is reflected in a SQL query joining several tables with different types of joins. The database will make a flat table, fetchAssoc returns the shape.

Example: Let's take a customer and order table (N:M binding) and query:

$result = $database->query('
  SELECT customer_id, customers.name, order_id, orders.number, ...
  FROM customers
  INNER JOIN orders USING (customer_id)
  WHERE ...
');

And we'd like to get a nested associative array by Customer ID and then Order ID:

$all = $result->fetchAssoc('customer_id|order_id');

// we will iterate like this:
foreach ($all as $customerId => $orders) {
   foreach ($orders as $orderId => $order) {
	   ...
   }
}

An associative descriptor has a similar syntax as when you type the array by assigning it to PHP. Thus 'customer_id|order_id' represents the assignment series $all[$customerId][$orderId] = $row; sequentially for all rows.

Sometimes it would be useful to associate by the customer's name instead of his ID:

$all = $result->fetchAssoc('name|order_id');

// the elements then proceeds like this:
$order = $all['Arnold Rimmer'][$orderId];

But what if there are more customers with the same name? The table should be in the form of:

$row = $all['Arnold Rimmer'][0][$orderId];
$row = $all['Arnold Rimmer'][1][$orderId];
...

So we can distinguish between multiple possible Rimmers using an array. The associative descriptor has a format similar to the assignment, with the sequence array representing []:

$all = $result->fetchAssoc('name[]order_id');

// we get all the Arnolds in the results
foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
   foreach ($arnoldOrders as $orderId => $order) {
	   ...
   }
}

Returning to the example with the customer_id|order_id descriptor, we will try to list the orders of each customer:

$all = $result->fetchAssoc('customer_id|order_id');

foreach ($all as $customerId => $orders) {
   echo "Customer $customerId":

   foreach ($orders as $orderId => $order) {
	   echo "ID number: $order->number";
	   // customer name is in $order->name
   }
}

It would be a nice to echo customer name too. But we would have to look for it in the $orders array. So let's adjust the results to such a shape:

$all[$customerId]->name = 'John Doe';
$all[$customerId]->order_id[$orderId] = $row;
$all[$customerId]->order_id[$orderId2] = $row2;

So, between $clientId and $orderId, we will also insert an intermediate item. This time not the numbered indexes as we used to distinguish between individual Rimmers, but a database row. The solution is very similar, just remember that the row symbolizes the arrow:

$all = $result->fetchAssoc('customer_id->order_id');

foreach ($all as $customerId => $row) {
   echo "Customer $row->name":

   foreach ($row->order_id as $orderId => $order) {
	   echo "ID number: $order->number";
   }
}

Prefixes & substitutions

Table and column names can contain variable parts. You will first define:

// create new substitution :blog:  ==>  wp_
$database->substitute('blog', 'wp_');

and then use it in SQL. Note that in SQL they are quoted by the colon:

$database->query("UPDATE [:blog:items] SET [text]='Hello World'");
// UPDATE `wp_items` SET `text`='Hello World'

Field data types

Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the Dibi\Type class.

$result->setType('id', Dibi\Type::INTEGER); // id will be integer
$row = $result->fetch();

is_int($row->id) // true

Logger

Dibi has a built-in logger that lets you track all SQL statements executed and measure the length of their duration. Activating the logger:

$database->connect([
	'driver'   => 'sqlite',
	'database' => 'sample.sdb',
	'profiler' => [
		'file' => 'file.log',
	],
]);

A more versatile profiler is a Tracy panel that is activated when connected to Nette.

Connect to Nette

In the configuration file, we will register the DI extensions and add the dibi section to create the required objects and also the database panel in the Tracy debugger bar.

extensions:
	dibi: Dibi\Bridges\Nette\DibiExtension3

dibi:
	host: localhost
	username: root
	password: ***
	database: foo
	lazy: true

Then the object of connection can be obtained as a service from the container DI, eg:

class Model
{
	private $database;

	public function __construct(Dibi\Connection $database)
	{
		$this->database = $database;
	}
}

dibi's People

Contributors

adawolfa avatar alesculek avatar brablc avatar dg avatar emanwebdev avatar enricodias avatar fprochazka avatar groupnet avatar hubipe avatar janpecha avatar janrossler avatar jantvrdik avatar jkrcma avatar jkuchar avatar kucix avatar mabar avatar milanpala avatar milo avatar o5 avatar ondrejmirtes avatar petrp avatar pilec avatar radekdostal avatar romansklenar avatar rydercz avatar seancsnyder avatar soukicz avatar vlki avatar whipstercz avatar zzromanzz 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  avatar  avatar  avatar  avatar  avatar  avatar

dibi's Issues

Class DibiNettePanel not loaded

Calling dibi::connect throws Fatal error in DibiConnection on line 123, stating that class DibiNettePanel does not exist. Possible solution is to simply require it:

require_once DIR."/../Nette/DibiNettePanel.php";

Zalamování textu SQL dotazu rozdělí datum a čas

Dibi automaticky zalamuje text SQL dotazu, zřejmě podle mezer, pokud mám porovnávání data pomocí DATE_FORMAT se zadaným datem, rozlomí mi dibi např. 2010-11-18 10:00:00 na dva řádky (stává se pouze v případě příliš dlouhého řádku předpokládám):

2010-11-18
10:00:00

což způsobuje to, že porovnání následně nefunguje. Ozkoušeno pouze na MySQL.

dibi.minified.com

There is not any dibi.minified.php file... Can it be added please?

Autoloading třídy \DibiNettePanel

Hledal jsem důvod, proč se mi nezobrazuje Debug Panel v Nette při použití dibi bez použití statického dibi::__něco__. Přestože mám v konfiguraci Debug Panel zapnutý, nezobrazí se.

O vytvoření a napojení DibiNettePanelu se stará kontruktor ve tříde DibiConnection. Je nějaký důvod, proč je se kontrola na vytvoření panelu provádí právě takto?

<?
if (class_exists('DibiNettePanel', FALSE)) {
    $panel = new DibiNettePanel(isset($profilerCfg['explain']) ? $profilerCfg['explain'] : TRUE, $filter);
    $panel->register($this);
}
?>

Osobně bych uvítal například toto řešení:

<?
if (defined('NETTE') && class_exists('DibiNettePanel')) {
    $panel = new DibiNettePanel(isset($profilerCfg['explain']) ? $profilerCfg['explain'] : TRUE, $filter);
    $panel->register($this);
}
?>

Pokud mám projekt postavený na Nette, tak se použije pro DibiNettePanel autoloading a všechno krásně běží. Pokud Nette používám jen jako pomocnou knihovnu, tak mě to zajímat nemusí, protože konstanta NETTE je edfinována v loader.php, který v tomto případě nepoužiju. A pokud panel pro Dibi nechci vůbec, tak to jednoduše vypnu v konfiguraci.

Pokud mám poslat Pull Request, stačí komentář.

TINYINT is detected as CHAR in MySQLi

DibiMySqliDriver::getColumnsMeta() detects TINYINT column as CHAR, because constants MYSQLI_TYPE_TINY and MYSQLI_TYPE_CHAR have same value.

(CHAR column is detected as MYSQLI_TYPE_STRING)

DibiProfiler a MAX_LENGTH

Dlzku SQL ovplyvnuje konstanta MAX_LENGTH. Je mi jasne, ze pokial profiler pouziva Firebug a FirePHP, je potrebne kontrolovat objem dat v hlavicke. Ale pri novom NetteBar-e uz take obmedzenie myslim nie je potrebne.

Samotny SQL profiler je potom zbytocny, kedze vacsinou prvych 500 znakov zo select-u je naprd, vacsinou to podstatne sa deje prave na konci :) Bolo by mozne dlzku SQL menit podla potreby ? Nieco ako Debug::$maxLen.

problem with dibi::getConnection()->getDatabaseInfo()->getTables() on empty db (postgre driver)

There is a problem getting array of tables if database is empty while using postgre driver.

Problem is in postgre.php in GetTables(). If there are no rows in resultSet, pg_fetch_all() returns FALSE . And this cause problems in DibiDatabaseInfo.php in init() method on line 115. Foreach expects it to be array instead and it throws warning 'Invalid argument supplied for foreach()'.

fix:


diff --git a/libs/dibi/drivers/postgre.php b/libs/dibi/drivers/postgre.php
index 6fbb560..f07d138 100644
--- a/libs/dibi/drivers/postgre.php
+++ b/libs/dibi/drivers/postgre.php
@@ -426,7 +426,7 @@ class DibiPostgreDriver extends DibiObject implements IDibiDriver
                ");
                $res = pg_fetch_all($this->resultSet);
                $this->free();
-               return $res;
+               return ($res === false ) ? array() : $res;
        }

Mysql(i) getAffectedRows()

V souboru: https://github.com/dg/dibi/blob/master/dibi/drivers/mysqli.php#L198 (metoda getAffectedRows) je uvedeno v komentari, ze metoda vraci FALSE pri chybe, ale podle PHP dokumentace a zdrojoveho kodu to neni pravda:

http://www.php.net/manual/en/mysqli.affected-rows.php
https://github.com/php/php-src/blob/master/ext/mysqli/mysqli_api.c (doufam, ze odkazuju spravne)

Pri chybe se vraci -1.

Mohl by nekdo overit, ze se nemylim?
Diky


Narazil jsem na to, kdyz jsem cetl: http://stackoverflow.com/questions/7368225/how-do-i-tell-when-a-mysql-update-was-successful-versus-actually-updated-data

chybny arg

DibiProfiler::__construct() L56
miesto call_user_func('Nette\Diagnostics\Debugger', $this);
ma byt call_user_func('Nette\Diagnostics\Debugger::addPanel', $this);

DibiConnection::connect() není public.

Při rozšiřování DibiConnection (extensionMethod) je potřeba se připojit k databazi, protože může být nastaveno lazy připojení.

Bylo by dobré kdyby DibiConnection::connect() bylo public (DibiConnection::disconnect() public je)

Momentálně to řeším velmi ošklivým $connection->sql('');

Úprava je k dispozici zde: PetrP/dibi@9494061

Incorrect table identifier quoting in PostgreSQL

This query:

WITH RECURSIVE user_privileges AS (
    SELECT
        ur1."id",
        ur1.id_user_parent,
        ur1.login,
        1 AS sort_order
    FROM
        "[user"] ur1
    WHERE
        ur1.login='guest'
    UNION
    SELECT
        ur2."id",
        ur2.id_user_parent,
        ur2.login,
        2 AS sort_order
    FROM
        "[user"] ur2
        JOIN
        user_privileges ups1
        ON(ur2.id_user_parent=ups1."id" OR ur2."id"=ups1.id_user_parent)
    WHERE
        ur2.login<>'guest'
)
SELECT
    *
FROM
    user_privileges
ORDER BY
    sort_order,
    "id",
    "id_user_parent"
;

which is the result of calling dibi::test($sql, UserModel::$user_login, UserModel::$user_login), where $sql is:

$sql = '
WITH RECURSIVE user_privileges AS (
    SELECT
        ur1.[id],
        ur1.id_user_parent,
        ur1.login,
        1 AS sort_order
    FROM
        [' . self::TABLE_USERS . '] ur1
    WHERE
        ur1.login=%s
    UNION
    SELECT
        ur2.[id],
        ur2.id_user_parent,
        ur2.login,
        2 AS sort_order
    FROM
        [' . self::TABLE_USERS . '] ur2
        JOIN
        user_privileges ups1
        ON(ur2.id_user_parent=ups1.[id] OR ur2.[id]=ups1.id_user_parent)
    WHERE
        ur2.login<>%s
)
SELECT
    *
FROM
    user_privileges
ORDER BY
    sort_order,
    [id],
    [id_user_parent]
';

and value of UserModel::$user_login is guest, fails, because Dibi doesn't rewrite [user] to "user" as it is necessary but it rewrites it to "[user"] instead.

If I use %n in $sql instead of [user], I get "[user]" instead of "user" (I call dibi::test($sql, self::TABLE_USERS, UserModel::$user_login, self::TABLE_USERS, UserModel::$user_login) in this case). This throws an SQL translate error exception.

Database: PostgreSQL.
Dibi: Dibi 1.3-dev (revision 550be3b released on 2010–04–06).

Mysql & Mysqli drivers: bug in detection unsigned columns

unsigned se špatně detekuje. Blbé pořadí parametrů ve funkci strstr viz
forum

Oprava zde: PetrP/dibi@9df5ec9

Zde je test:

/*
CREATE TABLE `test4` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `int` int(10) unsigned NOT NULL,
    `int2` int(10) unsigned zerofill NOT NULL,
    `int3` int(10) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB;
*/

$connetion = new DibiConnection(array(
    'driver' => 'mysql',
    'username' => 'root',
    'database' => 'test',
));

T::note(get_class($connetion->getDriver()));
foreach ($connetion->getDriver()->getColumns('test4') as $column)
{
    T::dump($column['unsigned'], $column['name']);
}

$connetion = new DibiConnection(array(
    'driver' => 'mysqli',
    'username' => 'root',
    'database' => 'test',
));

T::note(get_class($connetion->getDriver()));
foreach ($connetion->getDriver()->getColumns('test4') as $column)
{
    T::dump($column['unsigned'], $column['name']);
}


__halt_compiler() ?>
------EXPECT------
DibiMySqlDriver

id: bool(TRUE)

int: bool(TRUE)

int2: bool(TRUE)

int3: bool(FALSE)

DibiMySqliDriver

id: bool(TRUE)

int: bool(TRUE)

int2: bool(TRUE)

int3: bool(FALSE)

inserting blank row

$dibiConnection->insert("table", array());

generates sql query

insert into `table` () values (null)

which fails. It should generate

insert into `table` () values ()

I need it for inserting rows where id is automatically generated and where I don't need anything else.

Privátní proměnné třídy DibiDataSource

Navrhuji některé privátní proměnné této třídy změnit na protected, aby se s nimi dalo dále pracovat ať už v extension metodách nebo v potomcích. Už jsem se dostal vícekrát do situace, kdy by se to hodilo. Momentálně by se to hodilo k implementaci metody reverse().

BUG v substitucích, asi někde probíhá cache

Následující kód:

$connection = dibi::connect(Environment::getConfig('database'));

dibi::addSubst('id', 'A.id');
dump($connection->translate('[:id:]'));
dibi::removeSubst('id');

dibi::addSubst('id', 'B.id');
dump($connection->translate('[:id:]'));

vypíše:

"`A`.`id`" (8)
"`A`.`id`" (8)

očekávaný výstup je:

"`A`.`id`" (8)
"`B`.`id`" (8)

Verze: 1.3-dev 8dc164d released on 2010-08-05

Snad tohle pomůže: U toho translate dělají problém hranaté závorky, samotné ':id:' se překládá správně (tedy substituuje se za A i za B), problém je s identifikátorem, tj. '[:id:]'.

Bude to asi tím, že se cachují identifikátory jako třída DibiLazyStorage a identifikátor se substitucí tam přetrvá i když se substituce změní.

bad colon interpreting in sql in dibi::query($sql)

dibi::query('RENAME TABLE table TO table_2010-05-17_03:06:53;');
failed with "InvalidStateException - Missing substitution for '06' expression."

mysql do this command right, so this sql command is OK.

dibi won't connect to Nette's Debugbar

Problém je na řádku 28 v dibi.php, tato implementace háže:
Argument 1 passed to Nette\Debug::addPanel() must implement interface Nette\IDebugPanel, instance of DibiProfiler given

Opravou je z interface_exists('Nette\IDebugPanel', FALSE) vyhodit FALSE. Chyba se projevuje na Nette 2.0-dev z 24.9. v dibi balíku při aktivaci profileru pomocí config.ini.

DibiRow nededi od DibiObject

DibiRow v najnovsej verzii 1.3 nededi od DibiObjectu a iba implementuje potrebne rozhrania cim vsak obera o funkcionalitu objekty, ktore chce programator nastavit ako setRowClass objekty.

Bolo by lepsie ak by dedil od DibiObjectu aby aj classes, ktore budu v buducnu ako potencionalne entity mohli vyuzivat silu DibiObjectu.

lazy connection nefunguje s lazy disconnection

Připojuju se v nette 0.9.7 v bootstrap pomoci

$application->onStartup[] = 'BaseModel::connect';
$application->onShutdown[] = 'BaseModel::disconnect';

database.lazy = true

a kdyz se na databazi nedotazu vubec (kešuju), tak mi vyhodí disconnect warning:
mysqli_close() expects parameter 1 to be mysqli, null given in C:..\libs\dibi\drivers\mysqli.php on line 146

používám dibi 1.3 ale v latest to taky hází tohle

takže předpokládám, že connect je lazy, ale disconect s timhle vůbec nepočítá...

Komentáře v SQL dotazu

$result = dibi::query("-- ".__FILE__.":".__LINE__.": Hello world
                    SELECT 'hello world'
            ")->fetchSingle();

Výsledný SQL dotaz:

-- /var/www/index.php':83:' Hello world
SELECT 'hello world'

Ve výsledku jsou navíc apostrofy u dvojteček.

Přidáme-li otazník:

$result = dibi::query("-- ".__FILE__.":".__LINE__.": Hello world ?
                    SELECT 'hello world'
            ")->fetchSingle();

Výsledek: SQL translate error

-- /var/www/index.php':83:' Hello world **Extra
placeholder**
SELECT 'hello world'

Trošku větší SQL dotazy pak dokážou generovat i chyby jako "DateTime::__construct(): Failed to parse time string (AND start_time <= %d) at position 0 (A): The timezone could not be found in the database". Samozřejmě, SQL dotaz je zcela správně a po odstranění komentáře na prvním řádku funguje.

Ten komentář na začátku měl sloužit k identifikaci dotazu v logu a chybových hlášeních.

Navíc v tom ladicím toolbaru od Nette chybí "explain" u takto okomentovaného dotazu a komentáře nejsou správně zvýrazněny.

DateTime & NULL

Hodila by se kontrola co vrací DateTime::format() (nebo jeho potomek),

protože pokud vrátí přesně typ NULL (možná to zní divně, ale je to v určitých případech potřeba) tak to poskládá chybný dotaz.

Přecejenom sloupec typu 'timestamp' v databázi může být taky NULL

příklad: http://gist.github.com/644588

SQL injection přes limit v DibiFluent

Pokud do limitu v limitu v DibiFluent vložím SQL kód, tak se v klidu vykoná. Ono je to vlastně správně, protože v limit nemusí být jen číslo, ale může tam být výraz. Přesto ale v 99% případů tam bude jen číslo.

  $sql->limit(" 1; drop table inovice"); // projde a vykoná se

Správné použití by tedy nejspíš bylo

  $sql->limit("%i", " 1; drop table inovice");

Jenže to je dost psaní, když u všech limitů používám jen čísla. Ideální chování by tedy bylo, kdyby se limit validoval vždy jako int a jen přes modifikátor %sql by to šlo donutit k něčemu jinému. Samozřejmě je to ale BC break, takže by na to nejspíš musela být nějaká volba "strictLimits"

To samé pak platí pro offset. U jiných konstrukcí to problém není a pouze u limit/offset podvědomě očekávám automatickou validaci.

U sebe jsem to dočasně vyřešil následujícím kódem v DibiFulent->__call, ale určitě to není ideální řešení

if(($clause == "LIMIT" || $clause == "OFFSET") && is_array($args) && count($args) == 1 && $args[1] !== false) {
    $args = array((int)trim($args[0]));
}

EDIT: Konkrétně ten DROP TABLE v MySQL stejně neprojde, ale tohle už třeba ano:

  $sql->limit(" 1; union(select group_concat(user) from(mysql.user))"); 

detectTypes pro typ decimal vrací string

Pokud je sloupec typu decimal (8,3) a z databáze se čte celé číslo, mysql vrátí třeba "123.000". V dibi se detekuje jako decimal, takže se použije float. Na řádku 516 v DibiResult (metoda normalize) se ale provádí ještě jedna kontrola:

$row[$key] = (string) ($tmp = (float) $value) === $value ? $tmp : $value;

Pokud se pak vypisuje a ve sloupci decimal mám v jednom řádku hodnotu "123.001" a v druhém "123.000", tak první se převede správně na float, ale druhý se vrátí jako string.

Testováno na MySQL se stable dibi 2.0.1

MSSQL reflector - jiná hodnota pro neexistující položku size než v ostatních driverech

Metoda getColumns() v mssql.reflector.php vrací v položce „size“ v případě chybějících dat FALSE, ostatní drivery (mysql.reflector.php a sqlite.reflector) vrací v tomto případě NULL – lze vyřešit nastavením defaultní hodnoty $size na NULL tedy :

  1. $size = false;
    nahradit za:
  2. $size = NULL;

pak bude chování metody stejné jako při použití ostatních driverů.

DibiFluent cloning

Klonování DibiFluentu vůbec nefunguje.

$fluent = $dibiConnection->select("*")->from("table");
$f = clone $fluent;
echo $f;

nevypíše nic.

$fluent = $dibiConnection->select("*")->from("table");
$f = clone $fluent;
$f->removeClause("select")->select("count(*)");
echo $f;

pro změnu vypíše:

SELECT count(*) DISTINCT count(*) FROM count(*) WHERE count(*) GROUP BY count(*) HAVING count(*) ORDER BY count(*) LIMIT count(*) OFFSET count(*).

Mysql, BigInt a detekce typu

Pokud je v sloupeček typu BIGINT (vysledováno v MySQL databázi), dibi jej označí jako dibi::INTEGER (při automatickém přetypování). Pro 32b systémy je ale int v PHP jenom +/- 2 mld, tudíž se do něj hodnota bigintu z databáze nevejde a výsledek ve vrácených datech přeteče velikost intu (PHP_INT_SIZE). Pokud se provede přetypování na float, již je to v PHP v pořádku.

(SELECT ...) UNION (SELECT ...) není označen jako SELECT -> žádný explain

Mějme dotaz s UNIONem, který chceme třídit:

(SELECT a FROM t1 WHERE a=10 AND B=1)
UNION
(SELECT a FROM t2 WHERE a=11 AND B=2)
ORDER BY a LIMIT 10;

pak tento dotaz není označen jako IDibiProfiler::SELECT (viz DibiConnection.php 334) a tím pádem není nabídnuta možnost explainu dotazu v DibiProfileru.

Dibi Fluent a JOIN SELECT

Vypadá to na malý bug v Dibi fluent:

Pokud provedu

dibi::select('*')->from('jablka');

dostávám krásný výpis tabulky jablka.

Pokdu ale takovýto výpis chci joinnout:

echo dibi::select('*')->from('krabice_na_jablka', 'k')->join(
   dibi::select('*')->from('jablka'), 'j')->on('k.jablka_id = j.id');

Asi se to nějak zacyklí, protože to končí fatal errorem s vyčerpáním paměti.

Řešení problému je např. následující:

echo dibi::select('*')->from('krabice_na_jablka', 'k')->join(
   '('.dibi::select('*')->from('jablka').')', 'j')->on('k.jablka_id = j.id');

Tedy manuální dopsání závorek k joinovanému selectu - s ním vše funguje v pořádku.

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.