Compare commits

...

14 Commits

10 changed files with 165 additions and 98 deletions

6
.editorconfig Executable file
View File

@ -0,0 +1,6 @@
root = true
[README.md]
charset = utf-8
indent_style = tab
tab_width = 2

0
.gitignore vendored Normal file → Executable file
View File

View File

@ -2,8 +2,8 @@
Lightweight binary database by pure PHP<br> Lightweight binary database by pure PHP<br>
## Dependencies ## Dependencies
1. ![PHP 8.4](https://www.php.net/releases/8.4/en.php) 1. [PHP 8.4](https://www.php.net/releases/8.4/en.php)
2. ![Composer](https://getcomposer.org/) (php package manager) 2. [Composer](https://getcomposer.org/) (php package manager)
## Installation ## Installation
`composer require mirzaev/baza` `composer require mirzaev/baza`
@ -12,29 +12,29 @@ Lightweight binary database by pure PHP<br>
```php ```php
<?php <?php
use use mirzaev\baza\database, use mirzaev\baza\database,
mirzaev\baza\column, mirzaev\baza\column,
mirzaev\baza\record, mirzaev\baza\record,
mirzaev\baza\enumerations\encoding, mirzaev\baza\enumerations\encoding,
mirzaev\baza\enumerations\type; mirzaev\baza\enumerations\type;
// Initializing the database // Initializing the database
$database = new database() $database = new database()
->encoding(encoding::utf8) ->encoding(encoding::utf8)
->columns( ->columns(
new column('name', type::string, ['length' => 32]), new column('name', type::string, ['length' => 32]),
new column('second_name', type::string, ['length' => 64]), new column('second_name', type::string, ['length' => 64]),
new column('age', type::integer), new column('age', type::integer),
new column('height', type::float) new column('height', type::float)
) )
->connect(__DIR__ . DIRECTORY_SEPARATOR . 'database.ba'); ->connect(__DIR__ . DIRECTORY_SEPARATOR . 'database.ba');
// Initializing the record // Initializing the record
$record = $database->record( $record = $database->record(
'Arsen', 'Arsen',
'Mirzaev', 'Mirzaev',
23, 23,
(float) 165 (float) 165
); );
if ($database->write($record)) { if ($database->write($record)) {
@ -66,4 +66,4 @@ if ($database->write($record)) {
## Used by ## Used by
- My site-article about how i was kidnapped by PMC Wagner operatives [mirzaev/repression](https://git.svoboda.works/mirzaev/repression) - My site-article about how i was kidnapped by PMC Wagner operatives [mirzaev/repression](https://git.svoboda.works/mirzaev/repression)
- My decentralized P2P blockchain chats project [mirzaev/notchat](https://git.svoboda.works/mirzaev/notchat) - My decentralized P2P blockchain chats project [mirzaev/notchat](https://git.svoboda.works/mirzaev/notchat)
- Svoboda Telegram chat-robot [svoboda/negotiator](https://git.svoboda.works/svoboda/negotiator) - Svoboda Telegram chat-robot negotiator [svoboda/negotiator](https://git.svoboda.works/svoboda/negotiator)

0
mirzaev/baza/system/column.php Normal file → Executable file
View File

156
mirzaev/baza/system/database.php Normal file → Executable file
View File

@ -48,7 +48,7 @@ class database
* *
* @var string $database Path to the database file * @var string $database Path to the database file
*/ */
public protected(set) string $database = __DIR__ . DIRECTORY_SEPARATOR . 'database.ba'; public protected(set) string $database = __DIR__ . DIRECTORY_SEPARATOR . 'database.baza';
/** /**
* Backups * Backups
@ -259,48 +259,42 @@ class database
*/ */
public function unpack(array $binaries): record public function unpack(array $binaries): record
{ {
if (count($binaries) === count($this->columns)) { // Declaring the buffer of unpacked values
// Amount of binery values matches amount of columns $unpacked = [];
// Declaring the buffer of unpacked values foreach ($this->columns as $index => $column) {
$unpacked = []; // Iterating over columns
foreach (array_combine($binaries, $this->columns) as $binary => $column) { // Initializing link to the binary value
// Iterating over columns $binary = $binaries[$index] ?? null;
if ($column->type === type::string) { if ($column->type === type::string) {
// String // String
// Unpacking the value // Unpacking the value
$value = unpack($column->type->value . $column->length, $binary)[1]; $value = unpack($column->type->value . $column->length, $binary ?? str_repeat("\0", $column->length))[1];
// Deleting NULL-characters // Deleting NULL-characters
$unnulled = str_replace("\0", '', $value); $unnulled = str_replace("\0", '', $value);
// Encoding the unpacked value // Encoding the unpacked value
$encoded = mb_convert_encoding($unnulled, $this->encoding->value); $encoded = mb_convert_encoding($unnulled, $this->encoding->value);
// Writing into the buffer of readed values // Writing into the buffer of readed values
$unpacked[] = $encoded; $unpacked[] = $encoded;
} else { } else {
// Other types // Other types
// Writing into the buffer of readed values // Writing into the buffer of readed values
$unpacked[] = unpack($column->type->value, $binary)[1]; $unpacked[] = unpack($column->type->value, $binary ?? "\0")[1];
}
} }
// Implementing the record
$record = $this->record(...$unpacked);
// Exit (success)
return $record;
} else {
// Amount of binery values not matches amount of columns
// Exit (fail)
throw new exception_invalid_argument('Amount of binary values not matches amount of columns');
} }
// Implementing the record
$record = $this->record(...$unpacked);
// Exit (success)
return $record;
} }
/** /**
@ -374,10 +368,15 @@ class database
* *
* @return array|null Readed records * @return array|null Readed records
*/ */
public function read(?callable $filter = null, ?callable $update = null, bool $delete = false, int $amount = 1, int $offset = 0): ?array public function read(
{ ?callable $filter = null,
?callable $update = null,
bool $delete = false,
int $amount = 1,
int $offset = 0
): ?array {
// Opening the database file // Opening the database file
$file = fopen($this->database, 'r+b'); $file = fopen($this->database, 'c+b');
if (flock($file, LOCK_EX)) { if (flock($file, LOCK_EX)) {
// The file was locked // The file was locked
@ -408,6 +407,9 @@ class database
// Reading the binary value from the database // Reading the binary value from the database
$binaries[] = fread($file, $column->type->size()); $binaries[] = fread($file, $column->type->size());
} }
// Terminate loop when end of file is reached
/* if (feof($file)) break 2; */
} }
// Terminate loop when end of file is reached // Terminate loop when end of file is reached
@ -417,52 +419,60 @@ class database
// Unpacking the record // Unpacking the record
$record = $this->unpack($binaries); $record = $this->unpack($binaries);
if (is_null($filter) || $filter($record, $records)) { if ((bool) array_filter($record->values())) {
// Passed the filter // The record contains at least one non-empty value
if ($offset-- <= 0) { if (is_null($filter) || $filter($record, $records)) {
// Offsetted // Passed the filter
if ($delete) { if ($offset-- <= 0) {
// Requested deleting // Offsetted
// Moving to the beginning of the row if ($delete) {
fseek($file, -$this->length, SEEK_CUR); // Requested deleting
// Writing NUL-characters instead of the record to the database file // Moving to the beginning of the row
fwrite($file, str_repeat("\0", $this->length)); fseek($file, -$this->length, SEEK_CUR);
// Moving to the end of the row // Writing NUL-characters instead of the record to the database file
fseek($file, $this->length, SEEK_CUR); fwrite($file, str_repeat("\0", $this->length));
} else if ($update) {
// Requested updating
// Updating the record // Moving to the end of the row
$update($record); fseek($file, $this->length, SEEK_CUR);
} else if ($update) {
// Requested updating
// Packing the updated record // Updating the record
$packed = $this->pack($record); $update($record);
// Moving to the beginning of the row // Packing the updated record
fseek($file, -$this->length, SEEK_CUR); $packed = $this->pack($record);
// Writing to the database file // Moving to the beginning of the row
fwrite($file, $packed); fseek($file, -$this->length, SEEK_CUR);
// Moving to the end of the row // Writing to the database file
fseek($file, $this->length, SEEK_CUR); fwrite($file, $packed);
// Moving to the end of the row
fseek($file, $this->length, SEEK_CUR);
}
// Writing into the buffer of records
$records[] = $record;
// Decreasing the amount iterator
--$amount;
} }
// Writing into the buffer of records
$records[] = $record;
// Decreasing the amount iterator
--$amount;
} }
} else {
// The record contains only empty values
} }
} catch (exception_logic | exception_invalid_argument $exception) { } catch (exception_logic | exception_invalid_argument | exception_domain $exception) {
// Writing into the buffer of failed to reading records // Writing into the buffer of failed to reading records
/* $failed[] = $record; */
// Exit (fail)
throw new exception_runtime('Failed to processing the record', previous: $exception);
} }
} }
@ -480,6 +490,16 @@ class database
return null; return null;
} }
/**
* Count
*
* @return int Amount of records
*/
public function count(): int{
// Exit (success)
return $this->length > 0 && file_exists($this->database) ? filesize($this->database) / $this->length : 0;
}
/** /**
* Backups * Backups
* *

0
mirzaev/baza/system/enumerations/encoding.php Normal file → Executable file
View File

4
mirzaev/baza/system/enumerations/type.php Normal file → Executable file
View File

@ -45,8 +45,8 @@ enum type: string
{ {
// Exit (success) // Exit (success)
return match ($this) { return match ($this) {
type::char, type::string, type::short => 'string', type::string, type::short => 'string',
type::char_unsigned, type::short_unsigned, type::integer, type::integer_unsigned, type::long, type::long_unsigned, type::long_long, type::long_long_unsigned => 'integer', type::char, type::char_unsigned, type::short_unsigned, type::integer, type::integer_unsigned, type::long, type::long_unsigned, type::long_long, type::long_long_unsigned => 'integer',
type::float, type::double => 'double', type::float, type::double => 'double',
type::null => 'NULL', type::null => 'NULL',
default => throw new exception_unexpected_value('Not found the type') default => throw new exception_unexpected_value('Not found the type')

18
mirzaev/baza/system/record.php Normal file → Executable file
View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace mirzaev\baza; namespace mirzaev\baza;
// Built-in libraries
use DomainException as exception_domain;
/** /**
* Record * Record
* *
@ -63,12 +66,23 @@ class record
* @param string $name Name of the parameter * @param string $name Name of the parameter
* @param mixed $value Content of the parameter * @param mixed $value Content of the parameter
* *
* @throws exception_domain if not found the parameter
*
* @return void * @return void
*/ */
public function __set(string $name, mixed $value = null): void public function __set(string $name, mixed $value = null): void
{ {
// Writing the value and exit if (isset($this->values[$name])) {
$this->values[$name] = $value; // Initialized the parameter
// Writing the value and exit
$this->values[$name] = $value;
} else {
// Not initialized the parameter
// Exit (fail)
throw new exception_domain("Not found the parameter: $name");
}
} }
/** /**

2
mirzaev/baza/tests/.gitignore vendored Normal file → Executable file
View File

@ -1 +1 @@
temporary temporary/*

37
mirzaev/baza/tests/record.php Normal file → Executable file
View File

@ -1,16 +1,38 @@
<?php <?php
declare(strict_types=1);
use mirzaev\baza\database, use mirzaev\baza\database,
mirzaev\baza\record, mirzaev\baza\record,
mirzaev\baza\column, mirzaev\baza\column,
mirzaev\baza\enumerations\encoding, mirzaev\baza\enumerations\encoding,
mirzaev\baza\enumerations\type; mirzaev\baza\enumerations\type;
// Initializing path to the composer loader file (main project)
$autoload =
__DIR__ . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'vendor' . DIRECTORY_SEPARATOR .
'autoload.php';
// Reinitializing path to the composer loaded file (depencendy project)
if (!file_exists($autoload))
$autoload =
__DIR__ . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'autoload.php';
// Importing files of thr project and dependencies // Importing files of thr project and dependencies
require(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'); require($autoload);
// Initializing path to the database file // Initializing path to the database file
$file = __DIR__ . DIRECTORY_SEPARATOR . 'temporary' . DIRECTORY_SEPARATOR . 'database.ba'; $file = __DIR__ . DIRECTORY_SEPARATOR . 'temporary' . DIRECTORY_SEPARATOR . 'database.baza';
echo "Started testing\n\n\n"; echo "Started testing\n\n\n";
@ -37,9 +59,10 @@ $database = (new database())
new column('name', type::string, ['length' => 32]), new column('name', type::string, ['length' => 32]),
new column('second_name', type::string, ['length' => 64]), new column('second_name', type::string, ['length' => 64]),
new column('age', type::integer), new column('age', type::integer),
new column('height', type::float) new column('height', type::float),
new column('active', type::char)
) )
->connect(__DIR__ . DIRECTORY_SEPARATOR . 'temporary' . DIRECTORY_SEPARATOR . 'database.ba'); ->connect($file);
echo '[' . ++$action . "] Initialized the database\n"; echo '[' . ++$action . "] Initialized the database\n";
@ -48,7 +71,8 @@ $record = $database->record(
'Arsen', 'Arsen',
'Mirzaev', 'Mirzaev',
24, 24,
165.5 165.5,
1
); );
echo '[' . ++$action . "] Initialized the record\n"; echo '[' . ++$action . "] Initialized the record\n";
@ -60,6 +84,7 @@ echo '[' . ++$action . '][' . ++$test . '][' . ($record->name === 'Arsen' ? 'SUC
echo '[' . $action . '][' . ++$test . '][' . ($record->second_name === 'Mirzaev' ? 'SUCCESS' : 'FAIL') . "][\"second_name\"] Expected: \"Mirzaev\" (string). Actual: \"$record->second_name\" (" . gettype($record->second_name) . ")\n"; echo '[' . $action . '][' . ++$test . '][' . ($record->second_name === 'Mirzaev' ? 'SUCCESS' : 'FAIL') . "][\"second_name\"] Expected: \"Mirzaev\" (string). Actual: \"$record->second_name\" (" . gettype($record->second_name) . ")\n";
echo '[' . $action . '][' . ++$test . '][' . ($record->age === 24 ? 'SUCCESS' : 'FAIL') . "][\"age\"] Expected: \"24\" (integer). Actual: \"$record->age\" (" . gettype($record->age) . ")\n"; echo '[' . $action . '][' . ++$test . '][' . ($record->age === 24 ? 'SUCCESS' : 'FAIL') . "][\"age\"] Expected: \"24\" (integer). Actual: \"$record->age\" (" . gettype($record->age) . ")\n";
echo '[' . $action . '][' . ++$test . '][' . ($record->height === 165.5 ? 'SUCCESS' : 'FAIL') . "][\"height\"] Expected: \"165.5\" (double). Actual: \"$record->height\" (" . gettype($record->height) . ")\n"; echo '[' . $action . '][' . ++$test . '][' . ($record->height === 165.5 ? 'SUCCESS' : 'FAIL') . "][\"height\"] Expected: \"165.5\" (double). Actual: \"$record->height\" (" . gettype($record->height) . ")\n";
echo '[' . $action . '][' . ++$test . '][' . ($record->active === 1 ? 'SUCCESS' : 'FAIL') . "][\"active\"] Expected: \"1\" (integer). Actual: \"$record->active\" (" . gettype($record->active) . ")\n";
echo '[' . $action . "] The record parameters checks have been completed\n"; echo '[' . $action . "] The record parameters checks have been completed\n";
@ -77,6 +102,7 @@ $record_ivan = $database->record(
'Ivanov', 'Ivanov',
24, 24,
(float) 210, (float) 210,
0
); );
echo '[' . ++$action . "] Initialized the record\n"; echo '[' . ++$action . "] Initialized the record\n";
@ -92,6 +118,7 @@ $record_ivan = $database->record(
'Esenina', 'Esenina',
19, 19,
(float) 165, (float) 165,
1
); );
echo '[' . ++$action . "] Initialized the record\n"; echo '[' . ++$action . "] Initialized the record\n";