Skip to content

LMDB Support for driver flags #8892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ PHP NEWS

- DBA:
. Fixed LMDB driver memory leak on DB creation failure (Girgias)
. Fixed GH-8856 (dba: lmdb: allow to override the MDB_NOSUBDIR flag). (Girgias)

- Random:
. Fixed bug GH-9067 (random extension is not thread safe). (cmb)
Expand Down
7 changes: 6 additions & 1 deletion UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ PHP 8.2 UPGRADE NOTES
. Exposed multiple new constants from libcurl 7.62 to 7.80.
. Added new function curl_upkeep() to perform any connection upkeep checks.

- DBA:
. The LMDB Driver now accepts the DBA_LMDB_USE_SUB_DIR or DBA_LMDB_NO_SUB_DIR
flags to determine if it should create a sub directory or not when creating
a database file.

- OCI8:
. Added an oci8.prefetch_lob_size directive and oci_set_prefetch_lob()
function to tune LOB query performance by reducing the number of
Expand Down Expand Up @@ -183,7 +188,7 @@ PHP 8.2 UPGRADE NOTES

- DBA
. dba_open() and dba_popen() now have the following enforced function signature
dba_open(string $path, string $mode, ?string $handler = null, int $permission = 0o644, int $map_size = 0)
dba_open(string $path, string $mode, ?string $handler = null, int $permission = 0o644, int $map_size = 0, ?int $flags = null)
. dba_fetch()'s optional skip argument is now at the end in line with
PHP userland semantics its signature now is:
dba_fetch(string|array $key, $dba, int $skip = 0): string|false
Expand Down
27 changes: 21 additions & 6 deletions ext/dba/dba.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ static zend_string* php_dba_make_key(HashTable *key)

/* check whether the user has write access */
#define DBA_WRITE_CHECK(info) \
if((info)->mode != DBA_WRITER && (info)->mode != DBA_TRUNC && (info)->mode != DBA_CREAT) { \
php_error_docref(NULL, E_WARNING, "You cannot perform a modification to a database without proper access"); \
if ((info)->mode != DBA_WRITER && (info)->mode != DBA_TRUNC && (info)->mode != DBA_CREAT) { \
php_error_docref(NULL, E_WARNING, "Cannot perform a modification on a readonly database"); \
RETURN_FALSE; \
}

Expand Down Expand Up @@ -359,6 +359,7 @@ PHP_MINIT_FUNCTION(dba)
REGISTER_INI_ENTRIES();
le_db = zend_register_list_destructors_ex(dba_close_rsrc, NULL, "dba", module_number);
le_pdb = zend_register_list_destructors_ex(dba_close_pe_rsrc, dba_close_rsrc, "dba persistent", module_number);
register_dba_symbols(module_number);
return SUCCESS;
}
/* }}} */
Expand Down Expand Up @@ -478,10 +479,12 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent)
zend_string *handler_str = NULL;
zend_long permission = 0644;
zend_long map_size = 0;
zend_long driver_flags = DBA_DEFAULT_DRIVER_FLAGS;
bool is_flags_null = true;
zend_string *persistent_resource_key = NULL;

if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "PS|S!ll", &path, &mode, &handler_str,
&permission, &map_size)) {
if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "PS|S!lll!", &path, &mode, &handler_str,
&permission, &map_size, &driver_flags, &is_flags_null)) {
RETURN_THROWS();
}

Expand All @@ -503,6 +506,11 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent)
RETURN_THROWS();
}

if (!is_flags_null && driver_flags < 0) {
zend_argument_value_error(6, "must be greater or equal than 0");
RETURN_THROWS();
}

if (persistent) {
zend_resource *le;

Expand Down Expand Up @@ -720,6 +728,7 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent)
info->mode = modenr;
info->file_permission = permission;
info->map_size = map_size;
info->driver_flags = driver_flags;
info->flags = (hptr->flags & ~DBA_LOCK_ALL) | (lock_flag & DBA_LOCK_ALL) | (persistent ? DBA_PERSISTENT : 0);
info->lock.mode = lock_mode;

Expand Down Expand Up @@ -835,9 +844,15 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent)
}
}

if (error || hptr->open(info, &error) != SUCCESS) {
if (error || hptr->open(info, &error) == FAILURE) {
dba_close(info);
php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s%s%s", hptr->name, error?": ":"", error?error:"");
if (EXPECTED(!EG(exception))) {
if (error) {
php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s: %s", hptr->name, error);
} else {
php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s", hptr->name);
}
}
FREE_PERSISTENT_RESOURCE_KEY();
RETURN_FALSE;
}
Expand Down
14 changes: 12 additions & 2 deletions ext/dba/dba.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@

/** @generate-class-entries */

#ifdef DBA_LMDB
/** @var int */
const DBA_LMDB_USE_SUB_DIR = 0;
/**
* @var int
* @cvalue MDB_NOSUBDIR
*/
const DBA_LMDB_NO_SUB_DIR = UNKNOWN;
#endif

/** @return resource|false */
function dba_popen(string $path, string $mode, ?string $handler = null, int $permission = 0o644, int $map_size = 0) {}
function dba_popen(string $path, string $mode, ?string $handler = null, int $permission = 0o644, int $map_size = 0, ?int $flags = null) {}

/** @return resource|false */
function dba_open(string $path, string $mode, ?string $handler = null, int $permission = 0o644, int $map_size = 0) {}
function dba_open(string $path, string $mode, ?string $handler = null, int $permission = 0o644, int $map_size = 0, ?int $flags = null) {}

/** @param resource $dba */
function dba_close($dba): void {}
Expand Down
13 changes: 12 additions & 1 deletion ext/dba/dba_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ext/dba/dba_gdbm.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ DBA_OPEN_FUNC(gdbm)
if(gmode == -1)
return FAILURE; /* not possible */

dbf = gdbm_open(info->path, 0, gmode, filemode, NULL);
dbf = gdbm_open(info->path, /* int block_size */ 0, gmode, filemode, NULL);

if(dbf) {
info->dbf = pemalloc(sizeof(dba_gdbm_data), info->flags&DBA_PERSISTENT);
Expand Down
25 changes: 23 additions & 2 deletions ext/dba/dba_lmdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,33 @@ DBA_OPEN_FUNC(lmdb)
{
MDB_env *env;
MDB_txn *txn;
int rc, flags = MDB_NOSUBDIR;
int rc;
int mode = info->file_permission;
zend_long map_size = info->map_size;

ZEND_ASSERT(map_size >= 0);

/* By default use the MDB_NOSUBDIR flag */
int flags = MDB_NOSUBDIR;
/* Use flags passed by the user for driver flags */
if (info->driver_flags != DBA_DEFAULT_DRIVER_FLAGS) {
ZEND_ASSERT(info->driver_flags >= 0);
switch (info->driver_flags) {
case 0:
case MDB_NOSUBDIR:
flags = info->driver_flags;
break;
default:
zend_argument_value_error(6, "must be either DBA_LMDB_USE_SUB_DIR or DBA_LMDB_NO_SUB_DIR for LMDB driver");
return FAILURE;
}
}

/* Add readonly flag if DB is opened in read only mode */
if (info->mode == DBA_READER) {
flags |= MDB_RDONLY;
}

rc = mdb_env_create(&env);
if (rc) {
*error = mdb_strerror(rc);
Expand All @@ -69,7 +90,7 @@ DBA_OPEN_FUNC(lmdb)
return FAILURE;
}

rc = mdb_txn_begin(env, NULL, 0, &txn);
rc = mdb_txn_begin(env, NULL, /* flags */ MDB_RDONLY, &txn);
if (rc) {
mdb_env_close(env);
*error = mdb_strerror(rc);
Expand Down
4 changes: 4 additions & 0 deletions ext/dba/php_dba.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ typedef struct dba_info {
int fd;
int file_permission;
zend_long map_size;
/* -1 for default driver flags */
zend_long driver_flags;
/* private */
int flags; /* whether and how dba did locking and other flags*/
struct dba_handler *hnd;
dba_lock lock;
} dba_info;

#define DBA_DEFAULT_DRIVER_FLAGS -1

#define DBA_LOCK_READER (0x0001)
#define DBA_LOCK_WRITER (0x0002)
#define DBA_LOCK_CREAT (0x0004)
Expand Down
1 change: 1 addition & 0 deletions ext/dba/php_lmdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifdef DBA_LMDB

#include "php_dba.h"
#include <lmdb.h>

DBA_FUNCS(lmdb);

Expand Down
2 changes: 1 addition & 1 deletion ext/dba/tests/dba_cdb_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Warning: dba_open(): Driver initialization failed for handler: cdb: Update opera
Failed to open DB
Test 2

Warning: dba_insert(): You cannot perform a modification to a database without proper access in %sdba_cdb_001.php on line %d
Warning: dba_insert(): Cannot perform a modification on a readonly database in %sdba_cdb_001.php on line %d
Test 3

Warning: dba_open(): Driver initialization failed for handler: cdb: Update operations are not supported in %sdba_cdb_001.php on line %d
Expand Down
14 changes: 14 additions & 0 deletions ext/dba/tests/dba_flags_arg.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
DBA new flags ValueError test
--EXTENSIONS--
dba
--FILE--
<?php
try {
dba_open('irrelevant', 'c', 'handler', flags: -1);
} catch (\ValueError $e) {
echo $e->getMessage(), \PHP_EOL;
}
?>
--EXPECT--
dba_open(): Argument #6 ($flags) must be greater or equal than 0
76 changes: 76 additions & 0 deletions ext/dba/tests/dba_lmdb_flags.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
--TEST--
DBA LMDB handler flags test
--EXTENSIONS--
dba
--SKIPIF--
<?php
$handler = 'lmdb';
require_once __DIR__ .'/skipif.inc';
?>
--FILE--
<?php
$handler = 'lmdb';

// Pass bogus flag
try {
$db_file = dba_open('irrelevant', 'c', $handler, flags: 45);
} catch (\ValueError $e) {
echo $e->getMessage(), \PHP_EOL;
}

// Use current test folder
$db_filename = __DIR__;
$db_file = dba_open($db_filename, 'c', $handler, flags: DBA_LMDB_USE_SUB_DIR);
assert($db_file !== false);

// Check insertion of data
dba_insert("key1", "Content String 1", $db_file);
dba_insert("key2", "Content String 2", $db_file);
dba_insert("key3", "Third Content String", $db_file);
dba_insert("key4", "Another Content String", $db_file);
dba_insert("key5", "The last content string", $db_file);

// Remove some data
dba_delete("key3", $db_file);
dba_delete("key1", $db_file);

// Fetch data
$key = dba_firstkey($db_file);
$total_keys = 0;
while ($key) {
echo $key, ': ', dba_fetch($key, $db_file), \PHP_EOL;
$key = dba_nextkey($db_file);
$total_keys++;
}
echo 'Total keys: ', $total_keys, \PHP_EOL;
for ($i = 1; $i < 6; $i++) {
echo "Key $i exists? ", dba_exists("key$i", $db_file) ? "Y" : "N", \PHP_EOL;
}

// Replace second key data
dba_replace("key2", "Content 2 replaced", $db_file);
echo dba_fetch("key2", $db_file), \PHP_EOL;

// Close handler
dba_close($db_file);

?>
--CLEAN--
<?php
$db_filename = __DIR__ . '/data.mdb';
$db_loc_filename = __DIR__ . '/lock.mdb';
@unlink($db_filename);
@unlink($db_loc_filename);
?>
--EXPECT--
dba_open(): Argument #6 ($flags) must be either DBA_LMDB_USE_SUB_DIR or DBA_LMDB_NO_SUB_DIR for LMDB driver
key2: Content String 2
key4: Another Content String
key5: The last content string
Total keys: 3
Key 1 exists? N
Key 2 exists? Y
Key 3 exists? N
Key 4 exists? Y
Key 5 exists? Y
Content 2 replaced
37 changes: 37 additions & 0 deletions ext/dba/tests/dba_lmdb_readonly.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
DBA LMDB handler readonly test
--EXTENSIONS--
dba
--SKIPIF--
<?php
$handler = 'lmdb';
require_once __DIR__ .'/skipif.inc';
?>
--FILE--
<?php
$handler = 'lmdb';
$db_filename = __DIR__ . "/lmdb-readonly.dbm";

// Create DB
$db_file = dba_open($db_filename, "c", $handler);
assert($db_file !== false);
// Close handler
dba_close($db_file);

// Open in read only mode
$db_file = dba_open($db_filename, "r", $handler);
assert($db_file !== false);

// Try inserting
dba_insert("key1", "This is a test insert", $db_file);
dba_close($db_file);
?>
--CLEAN--
<?php
$db_filename = __DIR__ . "/lmdb-readonly.dbm";
@unlink($db_filename);
@unlink($db_filename.'.lck');
@unlink($db_filename.'-lock');
?>
--EXPECTF--
Warning: dba_insert(): Cannot perform a modification on a readonly database in %s on line %d