Compare commits
14 Commits
|
@ -0,0 +1,6 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[README.md]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = tab
|
||||||
|
tab_width = 2
|
40
README.md
40
README.md
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
temporary
|
temporary/*
|
||||||
|
|
|
@ -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";
|
||||||
|
|
Loading…
Reference in New Issue