Skip to content

Commit a855ad7

Browse files
committed
Implement BlockingDatabase as internal API to interface with extension
1 parent 1c3d3f2 commit a855ad7

File tree

2 files changed

+412
-0
lines changed

2 files changed

+412
-0
lines changed

src/Io/BlockingDatabase.php

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
namespace Clue\React\SQLite\Io;
4+
5+
use Clue\React\SQLite\DatabaseInterface;
6+
use Clue\React\SQLite\Result;
7+
use Evenement\EventEmitter;
8+
9+
/**
10+
* [Internal] The `BlockingDatabase` class is responsible for communicating with
11+
* the SQLite3 extension (ext-sqlite3) and mapping its API to async return values.
12+
*
13+
* @internal see DatabaseInterface instead
14+
* @see DatabaseInterface
15+
*/
16+
class BlockingDatabase extends EventEmitter implements DatabaseInterface
17+
{
18+
/** @var \SQLite3 */
19+
private $sqlite;
20+
21+
/** @var bool */
22+
private $closed = false;
23+
24+
/**
25+
* @param string $filename
26+
* @param ?int $flags
27+
* @throws \Exception
28+
* @throws \Error
29+
* @internal see Factory instead
30+
*/
31+
public function __construct($filename, $flags = null)
32+
{
33+
if ($flags === null) {
34+
$this->sqlite = new \SQLite3($filename);
35+
} else {
36+
$this->sqlite = new \SQLite3($filename, $flags);
37+
}
38+
}
39+
40+
public function exec($sql)
41+
{
42+
if ($this->closed) {
43+
return \React\Promise\reject(new \RuntimeException('Database closed'));
44+
}
45+
46+
// execute statement and suppress PHP warnings
47+
$ret = @$this->sqlite->exec($sql);
48+
49+
if ($ret === false) {
50+
return \React\Promise\reject(new \RuntimeException(
51+
$this->sqlite->lastErrorMsg()
52+
));
53+
}
54+
55+
$result = new Result();
56+
$result->changed = $this->sqlite->changes();
57+
$result->insertId = $this->sqlite->lastInsertRowID();
58+
59+
return \React\Promise\resolve($result);
60+
}
61+
62+
public function query($sql, array $params = array())
63+
{
64+
if ($this->closed) {
65+
return \React\Promise\reject(new \RuntimeException('Database closed'));
66+
}
67+
68+
// execute statement and suppress PHP warnings
69+
if ($params === []) {
70+
$result = @$this->sqlite->query($sql);
71+
} else {
72+
$statement = @$this->sqlite->prepare($sql);
73+
if ($statement === false) {
74+
$result = false;
75+
} else {
76+
assert($statement instanceof \SQLite3Stmt);
77+
foreach ($params as $index => $value) {
78+
if ($value === null) {
79+
$type = \SQLITE3_NULL;
80+
} elseif ($value === true || $value === false) {
81+
// explicitly cast bool to int because SQLite does not have a native boolean
82+
$type = \SQLITE3_INTEGER;
83+
$value = (int) $value;
84+
} elseif (\is_int($value)) {
85+
$type = \SQLITE3_INTEGER;
86+
} elseif (\is_float($value)) {
87+
$type = \SQLITE3_FLOAT;
88+
} elseif (\preg_match('/[\x00-\x08\x11\x12\x14-\x1f\x7f]/u', $value) !== 0) {
89+
$type = \SQLITE3_BLOB;
90+
} else {
91+
$type = \SQLITE3_TEXT;
92+
}
93+
94+
$statement->bindValue(
95+
\is_int($index) ? $index + 1 : $index,
96+
$value,
97+
$type
98+
);
99+
}
100+
$result = @$statement->execute();
101+
}
102+
}
103+
104+
if ($result === false) {
105+
return \React\Promise\reject(new \RuntimeException(
106+
$this->sqlite->lastErrorMsg()
107+
));
108+
}
109+
110+
assert($result instanceof \SQLite3Result);
111+
if ($result->numColumns() !== 0) {
112+
// Fetch all rows only if this result set has any columns.
113+
// INSERT/UPDATE/DELETE etc. do not return any columns, trying
114+
// to fetch the results here will issue the same query again.
115+
$rows = $columns = [];
116+
for ($i = 0, $n = $result->numColumns(); $i < $n; ++$i) {
117+
$columns[] = $result->columnName($i);
118+
}
119+
120+
while (($row = $result->fetchArray(\SQLITE3_ASSOC)) !== false) {
121+
$rows[] = $row;
122+
}
123+
} else {
124+
$rows = $columns = null;
125+
}
126+
$result->finalize();
127+
128+
$result = new Result();
129+
$result->changed = $this->sqlite->changes();
130+
$result->insertId = $this->sqlite->lastInsertRowID();
131+
$result->columns = $columns;
132+
$result->rows = $rows;
133+
134+
return \React\Promise\resolve($result);
135+
}
136+
137+
public function quit()
138+
{
139+
if ($this->closed) {
140+
return \React\Promise\reject(new \RuntimeException('Database closed'));
141+
}
142+
143+
$this->close();
144+
145+
return \React\Promise\resolve();
146+
}
147+
148+
public function close()
149+
{
150+
if ($this->closed) {
151+
return;
152+
}
153+
154+
$this->closed = true;
155+
$this->sqlite->close();
156+
157+
$this->emit('close');
158+
$this->removeAllListeners();
159+
}
160+
}

0 commit comments

Comments
 (0)