1<?php
2
3function check_skip_any() {
4    if (dba_handlers() === []) {
5        die('skip no handlers installed');
6    }
7    if (dba_handlers() === ['cdb']) {
8        die('skip only cdb installed which is not suitable');
9    }
10    if (dba_handlers() === ['cdb_make']) {
11        die('skip only cdb_make installed which is not suitable');
12    }
13}
14
15function check_skip(string $handler) {
16    $handlers = dba_handlers();
17    if ($handlers === []) {
18        die('skip no handlers installed');
19    }
20    if (!in_array($handler, $handlers)) {
21        $HND = strtoupper($handler);
22        die("skip $HND handler not available");
23    }
24}
25
26function get_any_handler(): string {
27    foreach (dba_handlers() as $handler) {
28        // Those are weird
29        if ($handler !== 'cdb' && $handler !== 'cdb_make' && $handler !== 'inifile') {
30            echo 'Using handler: "', $handler, '"', \PHP_EOL;
31            return $handler;
32        }
33    }
34    return 'should_not_happen';
35}
36function get_any_db(string $name) {
37    return dba_open($name, 'c', get_any_handler());
38}
39
40enum LockFlag: string {
41    case FileLock = 'l';
42    case DbLock = 'd';
43    case NoLock = '-';
44}
45
46function set_up_db_ex(string $handler, string $name, LockFlag $lock, bool $persistent = false) {
47    $lock_flag = $lock->value;
48    // Open file in creation/truncation mode
49    $func = $persistent ? 'dba_popen' : 'dba_open';
50
51    $db_file = $func($name, 'n'.$lock_flag, $handler);
52
53    if ($db_file === false) {
54        die("Failed to create DB");
55    }
56
57    // Insert some data
58    dba_insert("key1", "Content String 1", $db_file);
59    dba_insert("key2", "Content String 2", $db_file);
60    dba_insert("key3", "Third Content String", $db_file);
61    dba_insert("key4", "Another Content String", $db_file);
62    dba_insert("key5", "The last content string", $db_file);
63
64    // Insert date with array keys
65    dba_insert(["", "name9"], "Content String 9", $db_file);
66    dba_insert(["key10", "name10"] , "Content String 10", $db_file);
67    dba_insert("[key30]name30", "Content String 30", $db_file);
68
69    return $db_file;
70}
71
72function set_up_db(string $handler, string $name, LockFlag $lock = LockFlag::FileLock): void {
73    $db_file = set_up_db_ex($handler, $name, $lock);
74    // Close creation/truncation handler
75    dba_close($db_file);
76}
77
78function run_common_read_only_test($dbHandle): void {
79    $key = dba_firstkey($dbHandle);
80    $result = [];
81    while ($key) {
82        $result[$key] = dba_fetch($key, $dbHandle);
83        $key = dba_nextkey($dbHandle);
84    }
85    ksort($result);
86    var_dump($result);
87}
88
89function run_standard_tests_ex(string $handler, string $name, LockFlag $lock, bool $persistent = false): void
90{
91    $lock_flag = $lock->value;
92    set_up_db($handler, $name, $lock);
93    $db_writer = dba_open($name, 'w'.$lock_flag, $handler);
94    if ($db_writer === false) {
95        die("Failed to open DB for write");
96    }
97
98    echo 'Remove key 1 and 3', \PHP_EOL;
99    var_dump(dba_delete("key3", $db_writer));
100    var_dump(dba_delete("key1", $db_writer));
101
102    echo 'Try to remove key 1 again', \PHP_EOL;
103    var_dump(dba_delete("key1", $db_writer));
104
105    // Fetch and sort data. We sort to guarantee that the output is
106    // consistent across invocations and architectures. When iterating
107    // with firstkey() and nextkey(), several engines (GDBM, LMDB,
108    // QDBM) make no promise about the iteration order. Others (TCADB,
109    // DBM) explicitly state that the order is arbitrary. With GDBM at
110    // least, the order appears platform-dependent -- we have a report
111    // in Github issue 14786. GDBM's own test suite sorts this output,
112    // suggesting that sorting is a reasonable workaround for the issue.
113    $output = [];
114
115    $key = dba_firstkey($db_writer);
116    $total_keys = 0;
117    while ($key) {
118        $output[] = $key . ': ' . dba_fetch($key, $db_writer) . \PHP_EOL;
119        $key = dba_nextkey($db_writer);
120        $total_keys++;
121    }
122
123    sort($output, SORT_STRING);
124    foreach ($output as $line) {
125        echo $line;
126    }
127
128    echo 'Total keys: ', $total_keys, \PHP_EOL;
129    for ($i = 1; $i < 6; $i++) {
130        echo "Key $i exists? ", dba_exists("key$i", $db_writer) ? 'Y' : 'N', \PHP_EOL;
131    }
132
133    echo 'Replace second key data', \PHP_EOL;
134    var_dump(dba_replace('key2', 'Content 2 replaced', $db_writer));
135    echo dba_fetch('key2', $db_writer), \PHP_EOL;
136
137    // Check that read is possible when a lock is used
138    $test_flag = 't';
139    if ($lock === LockFlag::NoLock) {
140        // No point testing when we don't use locks
141        $test_flag = '';
142    }
143    $db_reader = @dba_open($name, 'r'.$lock_flag.$test_flag, $handler);
144    if ($db_reader === false) {
145        echo 'Read during write: not allowed', \PHP_EOL;
146    } else {
147        echo 'Read during write: allowed', \PHP_EOL;
148        dba_close($db_reader);
149    }
150
151    if (dba_insert('key number 6', 'The 6th value', $db_writer)) {
152        echo 'Expected: Added a new data entry', \PHP_EOL;
153    } else {
154        echo 'Unexpected: Failed to add a new data entry', \PHP_EOL;
155    }
156
157    if (dba_insert('key number 6', 'The 6th value inserted again would be an error', $db_writer)) {
158        echo 'Unexpected: Wrote data to already used key', \PHP_EOL;
159    } else {
160        echo 'Expected: Failed to insert data for already used key', \PHP_EOL;
161    }
162
163    echo 'Replace second key data', \PHP_EOL;
164    var_dump(dba_replace('key2', 'Content 2 replaced 2nd time', $db_writer));
165    echo 'Delete "key4"', \PHP_EOL;
166    var_dump(dba_delete('key4', $db_writer));
167    echo 'Fetch "key2": ', dba_fetch('key2', $db_writer), \PHP_EOL;
168    echo 'Fetch "key number 6": ', dba_fetch('key number 6', $db_writer), \PHP_EOL;
169    dba_close($db_writer); // when the writer is open at least db3 would fail because of buffered io.
170
171    $db_reader = dba_open($name, 'r'.$lock_flag, $handler);
172    run_common_read_only_test($db_reader);
173    dba_close($db_reader);
174
175    /* TODO popen test? Old code copied from the previous general test
176    if (($db_file = dba_popen($db_filename, 'r'.($lock_flag==''?'':'-'), $handler))!==FALSE) {
177        if ($handler == 'dbm' || $handler == "tcadb") {
178            dba_close($db_file);
179        }
180    }
181     */
182}
183
184const MODES = ['r', 'w', 'c', 'n'];
185const LOCKS = ['l', 'd', '-', '' /* No lock flag is like 'd' */];
186function run_creation_tests_ex(string $handler, string $file_suffix, string $pre_req): void
187{
188    $db_name = $handler . $file_suffix;
189    foreach (MODES as $mode) {
190        foreach (LOCKS as $lock) {
191            eval($pre_req);
192            $arg = $mode.$lock;
193            echo 'Mode parameter is "', $arg, '":', \PHP_EOL;
194            $db = dba_open($db_name, $arg, $handler);
195            if ($db !== false) {
196                assert(file_exists($db_name));
197                $status = dba_insert("key1", "This is a test insert", $db);
198                if ($status) {
199                    $fetch = dba_fetch("key1", $db);
200                    if ($fetch === false) {
201                        echo 'Cannot fetch insertion', \PHP_EOL;
202                    } else {
203                        echo $fetch, \PHP_EOL;
204                    }
205                } else {
206                    echo 'Insertion failed', \PHP_EOL;
207                }
208                dba_close($db);
209            } else {
210                echo 'Opening DB failed', \PHP_EOL;
211            }
212            cleanup_standard_db($db_name);
213        }
214    }
215}
216
217function run_creation_tests(string $handler): void
218{
219    $extension = $handler === 'tcadb' ? 'tch' : 'db';
220    /* Trying to open a non-existing file */
221    echo '=== OPENING NON-EXISTING FILE ===', \PHP_EOL;
222    run_creation_tests_ex($handler, '_not_existing.'.$extension, '');
223
224    /* Trying to open an existing db file */
225    echo '=== OPENING EXISTING DB FILE ===', \PHP_EOL;
226    run_creation_tests_ex($handler, '_existing.'.$extension, 'dba_open($db_name, "n", $handler);');
227
228    /* Trying to open an existing random file */
229    echo '=== OPENING EXISTING RANDOM FILE ===', \PHP_EOL;
230    run_creation_tests_ex($handler, '_random.txt', 'file_put_contents($db_name, "Dummy contents");');
231}
232
233function clean_creation_tests(string $handler): void {
234    $db_name = $handler . '_not_existing.db';
235    cleanup_standard_db($db_name);
236    $db_name = $handler . '_existing.db';
237    cleanup_standard_db($db_name);
238    $db_name = $handler . '_random.txt';
239    cleanup_standard_db($db_name);
240}
241
242function run_standard_tests(string $handler, string $name): void {
243    echo '=== RUNNING WITH FILE LOCK ===', \PHP_EOL;
244    ob_start();
245    set_up_db($handler, $name, LockFlag::FileLock);
246    run_standard_tests_ex($handler, $name, LockFlag::FileLock);
247    cleanup_standard_db($name);
248    $run1_output = ob_get_flush();
249    echo '=== RUNNING WITH DB LOCK (default) ===', \PHP_EOL;
250    ob_start();
251    set_up_db($handler, $name, LockFlag::DbLock);
252    run_standard_tests_ex($handler, $name, LockFlag::DbLock);
253    cleanup_standard_db($name);
254    $run2_output = ob_get_clean();
255    if ($run1_output === $run2_output) {
256        echo 'SAME OUTPUT AS PREVIOUS RUN', \PHP_EOL;
257    } else {
258        echo $run2_output;
259    }
260
261    echo '=== RUNNING WITH NO LOCK ===', \PHP_EOL;
262    ob_start();
263    set_up_db($handler, $name, LockFlag::NoLock);
264    run_standard_tests_ex($handler, $name, LockFlag::NoLock);
265    $run3_output = ob_get_clean();
266    if ($run2_output === $run3_output) {
267        echo 'SAME OUTPUT AS PREVIOUS RUN', \PHP_EOL;
268    } else if ($run2_output === str_replace( // If only the fact that the lock prevented reads
269            'Read during write: allowed',
270            'Read during write: not allowed',
271            $run3_output
272        )
273    ) {
274        echo 'SAME OUTPUT AS PREVIOUS RUN (modulo read during write due to no lock)', \PHP_EOL;
275    } else {
276        echo $run3_output;
277    }
278}
279
280// TODO Array keys insertion
281// TODO Run all lock flags
282function set_up_cdb_db_and_run(string $name): void {
283    set_up_db('cdb', $name);
284
285    $db_file = dba_open($name, 'rl', 'cdb');
286    if ($db_file === false) {
287        die("Failed to reopen DB");
288    }
289    for ($i = 1; $i < 6; $i++) {
290        echo "Key $i exists? ", dba_exists("key$i", $db_file) ? 'Y' : 'N', \PHP_EOL;
291    }
292    run_common_read_only_test($db_file);
293    dba_close($db_file);
294
295    echo '--NO-LOCK--', \PHP_EOL;
296    cleanup_standard_db($name);
297    set_up_db('cdb', $name, LockFlag::NoLock);
298    $db_file = dba_open($name, 'r-', 'cdb');
299    if ($db_file === false) {
300        die("Failed to reopen DB");
301    }
302    for ($i = 1; $i < 6; $i++) {
303        echo "Key $i exists? ", dba_exists("key$i", $db_file) ? 'Y' : 'N', \PHP_EOL;
304    }
305    run_common_read_only_test($db_file);
306}
307
308function cleanup_standard_db(string $name): void {
309    @unlink($name);
310    @unlink($name.'.lck');
311    @unlink($name.'-lock');
312}
313