quasilyte / ksqlite
基于FFI的SQLite库,可用于PHP和KPHP
Requires
- php: >=7.4
- quasilyte/kfinalize: 1.0.0
- vkcom/kphp-polyfills: ^1.0
Requires (Dev)
- phpunit/phpunit: 9.5.16
- vkcom/kphpunit: v1.0.0
- vkcom/ktest-script: ^0.7.0
This package is not auto-updated.
Last update: 2024-09-27 20:40:16 UTC
README
KSQLite是一个基于FFI的SQLite库,可用于PHP和KPHP。
安装
由于这是一个FFI库,它需要在运行时可用动态库。
安装步骤
- 在您的系统中安装libsqlite3(如果您还没有的话)
- 定位库文件并将其放置在
./ffilibs/libsqlite3
下 - 安装此composer包以在您的代码中使用KSQLite类
根据您的系统,您需要找到libsqlite3.so
、libsqlite3.dylib
或libsqlite3.dll
文件。然后您可以将它复制到应用程序根目录下的ffilibs
文件夹中(注意:没有扩展名后缀)。
如果您难以定位库文件,请使用辅助脚本
$ php -f locate_lib.php
library candidate: /lib/x86_64-linux/libsqlite3.so.0
library candidate: /lib/x86_64-linux/libsqlite3.so
run something like this to make it discoverable (unix):
mkdir -p ffilibs && ln -s /lib/x86_64-linux/libsqlite3.so ./ffilibs/libsqlite3
然后安装composer库本身
$ composer require quasilyte/ksqlite
注意
- 如果您想全局放置库文件/链接,请将
./ffilibs
设置为符号链接 - 您可能想将
ffilibs/
添加到您的gitignore中
示例
- quick_start.php - API基本功能的简单概述
- prepared_statements.php - 如何重用单个语句进行多个查询
- transactions.php - 如何使用事务以及一些最佳实践
- simple_site.php - 使用SQLite作为数据库提供简单TODO应用程序
使用PHP运行示例
$ php -d opcache.enable_cli=1\ -d opcache.preload=preload.php\ -f ./examples/transactions.php
使用KPHP运行示例
# Step 1: compile the example: $ kphp --mode cli --composer-root $(pwd) ./examples/transactions.php # Step 2: run the binary: $ ./kphp_out/cli
API参考
所有函数都以false
返回值报告错误(操作状态)。
当有多个结果需要返回时,返回一个如tuple(T, bool)
的元组,其中第二个元组元素是操作状态。
如果操作状态是false
,请使用KSQLite::getLastError()
获取实际的错误消息。
请注意,您只需要关心关闭已打开的数据库对象。没有其他资源需要最终化。API的设计方式是您不会获得任何FFI分配的对象,因此库可以为您管理这些资源。
- exec方法在丢弃其结果的同时执行查询
- fetch方法收集并返回结果
- query方法是一个低级结果集迭代原语;fetch方法基于它构建
exec
function exec(string $sql, array $params = []): bool
$sql
SQL查询字符串,带有可选的占位符$params
查询的绑定变量
何时使用:需要执行一次查询,但不需要结果。
// Simple case: not bind params. $query = 'CREATE TABLE IF NOT EXISTS languages( lang_id INTEGER PRIMARY KEY, lang_name TEXT NOT NULL );' if (!$db->exec($query)) { handle_error($db->getLastError()); } // Exec with named params. // Note: a var prefix (':', '@' or '$') should be consistent // between the query and bind params array. $query = 'INSERT INTO languages(lang_name) VALUES(:lang_name)'; $params = [':lang_name' => 'KPHP']; if (!$db->exec($query, $params)) { handle_error($db->getLastError()); } // Exec with positional params. // Note: bind var numbers start from 1. $query = 'DELETE FROM languages WHERE lang_name = ?1 OR lang_name = ?2'; $params = [1 => 'COBOL', 2 => 'Visual COBOL']; if (!$db->exec($query, $params)) { handle_error($db->getLastError()); }
execPrepared
function execPrepared(string $sql, $bind_params_func): bool
$sql
SQL查询字符串,带有可选的占位符$bind_params_func
一个用于绑定查询变量的回调
何时使用:运行单个SQL语句,带有不同的参数,不需要结果。
// Execute several inserts with different bind var sets. $values = [10, 20, 30, 40]; $query = 'INSERT INTO fav_numbers(num_value) VALUES(?1)'; $ok = $db->execPrepared($query, function(KSQLiteParamsBinder $b) use ($values) { if ($b->query_index >= count($values)) { return false; // No more rows to insert, stop now } // Bind ?1 to the specified value. // Use string keys, like ':num_value', to bind named params. $b->bind(1, $values[$b->query_index]); return true; // Parameters bound, execute the query }); if (!$ok) { handle_error($db->getLastError()); } // Execute 10 inserts without bind vars. $query = "INSERT INTO fav_events(event_time) VALUES(time('now'))"; $ok = $db->execPrepared($query, function(KSQLiteParamsBinder $b) { return $b->query_index < 10; }); if (!$ok) { handle_error($db->getLastError()); } // Prepared statement API allows you to perform N queries // using the same statement even if you don't know the exact // N in advance. $query = "INSERT INTO important_data(x, y) VALUES(:x, :y)"; $ok = $db->execPrepared($query, function(KSQLiteParamsBinder $b) use ($stream) { // Note: we're not even using $b->index here as our data stream is statefull // and it knows which item we're processing right now. if (!$stream->hasMore()) { return false; } $stream->next(); foreach ($stream->keyValue() as $k => $v) { $b->bind($k, $v); } return true; }); if (!$ok) { handle_error($db->getLastError()); }
如果您觉得预处理语句API太低级,请考虑将其包装在辅助函数中。
fetch
function fetch(string $sql, array $params = [], $row_func = null): tuple(mixed, bool)
$sql
SQL查询字符串,带有可选的占位符$params
查询的绑定变量$row_func
一个用于每行的回调,其返回值被收集
如果$row_func
为null,则使用默认映射行为(rowAssoc
)。
何时使用:执行一次查询,收集结果。
// The simplest case: no bind params, default mapping function, collecting all results. // The result rows are arrays of [x, y]. $query = 'SELECT x, y FROM tab'; [$rows, $ok] = $db->fetch($query); if (!$ok) { handle_error($db->getLastError()); } foreach ($rows as $i => [$x, $y]) { var_dump([$i => "x=$x y=$y"]); } // Using the same query, but building the result with assoc arrays, // like ['x' => $x, 'y' => $y]. [$rows, $ok] = $db->fetch($query, [], function(KSQLiteQueryContext $ctx) { return $ctx->rowDataAssoc(); }); if (!$ok) { handle_error($db->getLastError()); } foreach ($rows as $i => $data) { var_dump([$i => "x=$data['x'] y=$data['y']"]); } // If you return a non-array value from fetch, you'll get a flat array in the final result. $query = 'SELECT id, second_key FROM users WHERE age >= ?1'; $vars = [1 => 18]; [$ids, $ok] = $db->fetch($query, $vars, function(KSQLiteQueryContext $ctx) { return $ctx->rowDataAssoc()['id']; }); if (!$ok) { handle_error($db->getLastError()); } foreach ($ids as $i => $id) { var_dump([$i => "id=$id"]); }
注意
- 您可以使用
$ctx->stop()
停止结果获取 - 如果您的查询没有绑定变量,但需要自定义的
$row_func
,请使用空数组作为$params
fetchRow
function fetchRow(string $sql, array $params = []): tuple(mixed[], bool)
$sql
SQL查询字符串,带有可选的占位符$params
查询的绑定变量
使用场景:执行一次查询,收集精确的一行结果。
$query = 'SELECT * FROM users WHERE user_id = :id'; [$user, $ok] = $db->fetchRow($query, [':id' => $id]); if (!$ok) { handle_error($db->getLastError()); }
注意:如果查询返回多行,将会报错。可以使用 LIMIT 1
或其他方式从数据库中请求仅一行,或者使用 fetch()
方法并显式跳过其余的行。
fetchRowAssoc
与 fetchRow
类似,但结果数组使用列名作为键而不是索引。
fetchColumn
function fetchColumn(string $sql, array $params = []): tuple(mixed, bool)
$sql
SQL查询字符串,带有可选的占位符$params
查询的绑定变量
使用场景:执行一次查询,收集精确的一列结果。
$query = 'SELECT COUNT(*) FROM users'; [$num_users, $ok] = $db->fetchColumn($query); if (!$ok) { handle_error($db->getLastError()); }
注意:如果查询返回多行或该行包含多个值,将会报错。
query
function query(string $sql, array $params, $row_func): bool
$sql
SQL查询字符串,带有可选的占位符$params
查询的绑定变量$row_func
一个无返回值的回调函数,对每一行调用
与 fetch-style API 不同,它本身不收集任何结果。使用外部状态来完成。
使用场景:当需要查询结果,但 fetch API 不够灵活时。
// Implementing a fetch-like operation via query. $result = new KSQLiteArray(); $ok = $db->query($sql, $vars, function(SQLiteQueryContext $ctx) use ($result) { $result->values[] = $ctx->rowData(); }); if (!$ok) { handle_error($db->getLastError()); } $handler->processData($result->values); // Work with unboxed [K]PHP array
在这里我们使用 KSQLiteArray 而不是普通数组,因为 KPHP 不支持按引用捕获闭包。
queryPrepared
function queryPrepared(string $sql, $bind_params_func, $row_func): bool
$sql
SQL查询字符串,带有可选的占位符$bind_params_func
一个用于绑定查询变量的回调$row_func
一个无返回值的回调函数,对每一行调用
使用场景:与 execPrepared
有相同的优点,但这里可以收集结果。
$ok = $db->queryPrepared( 'SELECT * FROM fav_numbers WHERE num_id = :num_id', function(KSQLiteParamsBinder $b) use ($ids) { if ($b->query_index >= count($ids)) { return false; } $b->bind(':num_id', $ids[$b->query_index]); return true; }, function(KSQLiteQueryContext $ctx) { // $ctx->query_index is 0 for the first prepared query execution. // The second execution will have $query_index=1 and so on. var_dump($ctx->query_index . '=>' . $ctx->rowDataAssoc()['num_value']); } ); if (!$ok) { handle_error($db->getLastError()); }