quasilyte/ksqlite

基于FFI的SQLite库,可用于PHP和KPHP

v1.0.0 2022-03-11 10:04 UTC

This package is not auto-updated.

Last update: 2024-09-27 20:40:16 UTC


README

Build Status

KSQLite是一个基于FFI的SQLite库,可用于PHP和KPHP。

安装

由于这是一个FFI库,它需要在运行时可用动态库。

安装步骤

  1. 在您的系统中安装libsqlite3(如果您还没有的话)
  2. 定位库文件并将其放置在./ffilibs/libsqlite3
  3. 安装此composer包以在您的代码中使用KSQLite类

根据您的系统,您需要找到libsqlite3.solibsqlite3.dyliblibsqlite3.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中

示例

使用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());
}