647 lines
23 KiB
PHP
647 lines
23 KiB
PHP
<?php
|
|
/** @noinspection SqlResolve, SqlWithoutWhere */
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Database;
|
|
|
|
use App\Libs\Config;
|
|
use App\Libs\Database\DBLayer;
|
|
use App\Libs\Database\PDO\PDOAdapter;
|
|
use App\Libs\Exceptions\DBLayerException;
|
|
use App\Libs\Exceptions\ErrorException;
|
|
use App\Libs\Exceptions\RuntimeException;
|
|
use App\Libs\Guid;
|
|
use App\Libs\TestCase;
|
|
use Monolog\Handler\TestHandler;
|
|
use Monolog\Logger;
|
|
use PDO;
|
|
use PDOException;
|
|
use Throwable;
|
|
use TypeError;
|
|
|
|
class DBLayerTest extends TestCase
|
|
{
|
|
private DBLayer|null $db = null;
|
|
protected TestHandler|null $handler = null;
|
|
|
|
private function createDB(PDO $pdo): void
|
|
{
|
|
$pdo->exec('DROP TABLE IF EXISTS "test"');
|
|
$pdo->exec(
|
|
<<<SQL
|
|
CREATE TABLE "test" (
|
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
"name" TEXT NULL,
|
|
"watched" INTEGER NULL DEFAULT 0,
|
|
"added_at" INTEGER NULL,
|
|
"updated_at" INTEGER NULL,
|
|
"json_data" JSON NULL,
|
|
"nullable" TEXT NULL
|
|
)
|
|
SQL
|
|
);
|
|
$pdo->exec('DROP TABLE IF EXISTS "fts_table"');
|
|
$pdo->exec('CREATE VIRTUAL TABLE "fts_table" USING fts5( name, json_data);');
|
|
}
|
|
|
|
public function setUp(): void
|
|
{
|
|
$this->handler = new TestHandler();
|
|
$logger = new Logger('logger');
|
|
$logger->pushHandler($this->handler);
|
|
Guid::setLogger($logger);
|
|
|
|
if (null === Config::get('database', null)) {
|
|
Config::init([
|
|
'database' => ag(require __DIR__ . '/../../config/config.php', 'database', [])
|
|
]);
|
|
}
|
|
|
|
$this->db = new DBLayer(new PDO(dsn: 'sqlite::memory:', options: Config::get('database.options', [])));
|
|
$this->createDB($this->db->getBackend());
|
|
|
|
foreach (Config::get('database.exec', []) as $cmd) {
|
|
$this->db->exec($cmd);
|
|
}
|
|
}
|
|
|
|
public function test_exec()
|
|
{
|
|
$this->checkException(
|
|
closure: fn() => $this->db->exec('SELECT * FROM movies'),
|
|
reason: 'Should throw an exception when an error occurs and no on_failure handler is set.',
|
|
exception: DBLayerException::class,
|
|
exceptionMessage: 'no such table',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->exec(
|
|
sql: 'SELECT * FROM movies',
|
|
options: ['on_failure' => fn(Throwable $e) => throw new ErrorException('Error occurred')]
|
|
),
|
|
reason: 'the on_failure handler should be called when an error occurs.',
|
|
exception: ErrorException::class,
|
|
exceptionMessage: 'Error occurred',
|
|
);
|
|
|
|
$this->assertSame(0, $this->db->exec('DELETE FROM test'));
|
|
}
|
|
|
|
public function test_query()
|
|
{
|
|
$this->checkException(
|
|
closure: fn() => $this->db->query(sql: 'SELECT * FROM movies'),
|
|
reason: 'Should throw an exception when an error occurs and no on_failure handler is set.',
|
|
exception: DBLayerException::class,
|
|
exceptionMessage: 'no such table',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->query(
|
|
sql: 'SELECT * FROM movies',
|
|
options: ['on_failure' => fn(Throwable $e) => throw new ErrorException('Error occurred')]
|
|
),
|
|
reason: 'the on_failure handler should be called when an error occurs.',
|
|
exception: ErrorException::class,
|
|
exceptionMessage: 'Error occurred',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: function () {
|
|
$options = Config::get('database.options', []);
|
|
$options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT;
|
|
|
|
$pdo = new PDO(dsn: 'sqlite::memory:', options: $options);
|
|
$db = new DBLayer($pdo);
|
|
|
|
foreach (Config::get('database.exec', []) as $cmd) {
|
|
$this->db->exec($cmd);
|
|
}
|
|
|
|
new PDOAdapter(new Logger('test'), $this->db)->migrations('up');
|
|
|
|
return $db->query(sql: 'SELECT * FROM test WHERE zid = :id');
|
|
},
|
|
reason: 'If PDO error mode is set to silent mode, failing to prepare a statement should still throw an exception.',
|
|
exception: DBLayerException::class,
|
|
exceptionMessage: 'Unable to prepare statement.',
|
|
);
|
|
}
|
|
|
|
public function test_transactions_operations()
|
|
{
|
|
$this->db->start();
|
|
$this->assertTrue($this->db->inTransaction(), 'Should be in transaction.');
|
|
$this->assertFalse($this->db->start(), 'Should not start a new transaction if we are already in one.');
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test', 'seq' => 1]);
|
|
$this->assertSame('1', $this->db->lastInsertId(), 'Should return last insert id.');
|
|
$this->db->rollBack();
|
|
$this->assertFalse($this->db->inTransaction(), 'Should not be in transaction.');
|
|
|
|
$this->db->start();
|
|
$this->assertCount(
|
|
0,
|
|
$this->db->select('sqlite_sequence')->fetchAll(),
|
|
'Should not have any records, as we rolled back.'
|
|
);
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test', 'seq' => 1]);
|
|
$this->assertSame('1', $this->db->lastInsertId(), 'Should return last insert id.');
|
|
$this->db->commit();
|
|
$this->assertFalse($this->db->inTransaction(), 'Should not be in transaction.');
|
|
$this->assertCount(
|
|
1,
|
|
$this->db->select('sqlite_sequence')->fetchAll(),
|
|
'Should have one record, as we committed.'
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->transactional(function (DBLayer $db) {
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test2', 'seq' => 1]);
|
|
$db->insert('not_set', ['name' => 'test', 'seq' => 1]);
|
|
}),
|
|
reason: 'Should throw an exception when trying to commit without starting a transaction.',
|
|
exception: DBLayerException::class,
|
|
exceptionMessage: 'no such table',
|
|
);
|
|
|
|
$this->assertCount(
|
|
1,
|
|
$this->db->select('sqlite_sequence')->fetchAll(),
|
|
'Should have one record, as the previous transaction was rolled back.'
|
|
);
|
|
|
|
$ret = $this->db->transactional(function (DBLayer $db) {
|
|
return $db->insert('sqlite_sequence', ['name' => 'test2', 'seq' => 1]);
|
|
});
|
|
|
|
$this->assertSame(1, $ret->rowCount(), 'Should return the number of affected rows.');
|
|
}
|
|
|
|
public function test_insert()
|
|
{
|
|
$this->checkException(
|
|
closure: fn() => $this->db->insert('test', []),
|
|
reason: 'Should throw exception if conditions parameter is empty.',
|
|
exception: RuntimeException::class
|
|
);
|
|
}
|
|
|
|
public function test_delete()
|
|
{
|
|
$this->checkException(
|
|
closure: fn() => $this->db->delete('test', []),
|
|
reason: 'Should throw exception if conditions parameter is empty.',
|
|
exception: RuntimeException::class
|
|
);
|
|
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test', 'seq' => 1]);
|
|
try {
|
|
$this->assertSame(
|
|
1,
|
|
$this->db->delete('sqlite_sequence', ['name' => 'test'], options: [
|
|
'limit' => 1,
|
|
'ignore_safety' => true
|
|
])->rowCount(),
|
|
'Should return the number of affected rows.'
|
|
);
|
|
} catch (DBLayerException $e) {
|
|
if (str_contains($e->getMessage(), 'near "LIMIT": syntax error') && 'sqlite' === $this->db->getDriver()) {
|
|
$this->assertSame(
|
|
1,
|
|
$this->db->delete('sqlite_sequence', ['name' => 'test'])->rowCount(),
|
|
'Should return the number of affected rows.'
|
|
);
|
|
} else {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function test_getCount()
|
|
{
|
|
$this->assertSame(
|
|
0,
|
|
$this->db->getCount('sqlite_sequence'),
|
|
'Should return the number of records in the table.'
|
|
);
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test', 'seq' => 1]);
|
|
$total = $this->db->getCount('sqlite_sequence', [
|
|
'seq' => [DBLayer::IS_HIGHER_THAN_OR_EQUAL, 1]
|
|
], options: [
|
|
'groupby' => ['name'],
|
|
'orderby' => ['name' => 'ASC'],
|
|
]);
|
|
$this->assertSame(1, $total, 'Should return the number of records in the table.');
|
|
$this->assertSame($total, $this->db->totalRows(), 'Should return the number of records in the table.');
|
|
|
|
$this->db->delete('sqlite_sequence', ['name' => 'test']);
|
|
$this->assertSame(
|
|
0,
|
|
$this->db->getCount('sqlite_sequence'),
|
|
'Should return the number of records in the table.'
|
|
);
|
|
}
|
|
|
|
public function test_update()
|
|
{
|
|
$this->checkException(
|
|
closure: fn() => $this->db->update('test', [], ['id' => 1]),
|
|
reason: 'Should throw exception if conditions parameter is empty.',
|
|
exception: RuntimeException::class
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->update('test', ['name' => 'test'], []),
|
|
reason: 'Should throw exception if conditions parameter is empty.',
|
|
exception: RuntimeException::class
|
|
);
|
|
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test', 'seq' => 1]);
|
|
$this->assertSame(
|
|
1,
|
|
$this->db->update('sqlite_sequence', ['seq' => 2], ['name' => 'test'])->rowCount(),
|
|
'Should return the number of affected rows.'
|
|
);
|
|
try {
|
|
$this->assertSame(
|
|
1,
|
|
$this->db->update('sqlite_sequence', ['seq' => 1], ['name' => 'test'], options: [
|
|
'limit' => 1,
|
|
'ignore_safety' => true
|
|
])->rowCount(),
|
|
'Should return the number of affected rows.'
|
|
);
|
|
} catch (DBLayerException $e) {
|
|
if (str_contains($e->getMessage(), 'near "LIMIT": syntax error') && 'sqlite' === $this->db->getDriver()) {
|
|
$this->assertSame(
|
|
1,
|
|
$this->db->update('sqlite_sequence', ['seq' => 1], ['name' => 'test'])->rowCount(),
|
|
'Should return the number of affected rows.'
|
|
);
|
|
} else {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function test_quote()
|
|
{
|
|
if ('sqlite' === $this->db->getDriver()) {
|
|
$this->assertEquals("'test'", $this->db->quote('test'), "Should return 'test'.");
|
|
$this->assertSame("'''test'''", $this->db->quote("'test'"), "Should return ''''test''''.");
|
|
$this->assertSame("'\"test\"'", $this->db->quote('"test"'), "Should return '\"test\"'.");
|
|
}
|
|
}
|
|
|
|
public function test_id()
|
|
{
|
|
$this->db->insert('sqlite_sequence', ['name' => 'test', 'seq' => 1]);
|
|
$this->assertSame('1', $this->db->id('test'), 'Should return the last insert id.');
|
|
$this->assertSame('1', $this->db->id(), 'Should return the last insert id.');
|
|
}
|
|
|
|
public function test_escapeIdentifier()
|
|
{
|
|
$this->checkException(
|
|
closure: fn() => $this->db->escapeIdentifier(''),
|
|
reason: 'Should throw exception if the identifier is empty.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'Column/table must be valid ASCII code'
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->escapeIdentifier('😊'),
|
|
reason: 'Should throw exception if the identifier contains non-ASCII characters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'Column/table must be valid ASCII code.'
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->escapeIdentifier('1foo'),
|
|
reason: 'Should throw exception if the identifier contains non-ASCII characters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'Must begin with a letter or underscore'
|
|
);
|
|
|
|
$this->assertSame('foo', $this->db->escapeIdentifier('foo'), 'Should return foo if quote is off.');
|
|
|
|
if ('sqlite' === $this->db->getDriver()) {
|
|
$this->assertSame('"foo"', $this->db->escapeIdentifier('foo', true), 'Should return "foo".');
|
|
$this->assertSame(
|
|
'""foo"."bar""',
|
|
$this->db->escapeIdentifier('"foo"."bar"', true),
|
|
'Should return ""foo"."bar"".'
|
|
);
|
|
}
|
|
}
|
|
|
|
public function test_getBackend()
|
|
{
|
|
$this->assertInstanceOf(PDO::class, $this->db->getBackend(), 'Should return the PDO instance.');
|
|
}
|
|
|
|
public function test_select()
|
|
{
|
|
$this->db->insert('test', [
|
|
'name' => 'test',
|
|
'watched' => 1,
|
|
'added_at' => 1,
|
|
'updated_at' => 2,
|
|
'json_data' => json_encode([
|
|
'my_id' => 1,
|
|
'my_name' => 'test',
|
|
'my_data' => [
|
|
'my_id' => 1,
|
|
'my_name' => 'test',
|
|
],
|
|
]),
|
|
]);
|
|
|
|
$this->db->insert('test', [
|
|
'name' => 'test2',
|
|
'watched' => 0,
|
|
'added_at' => 3,
|
|
'updated_at' => 4,
|
|
'json_data' => json_encode([
|
|
'my_id' => 2,
|
|
'my_name' => 'test2',
|
|
'my_data' => [
|
|
'my_id' => 2,
|
|
'my_name' => 'test2',
|
|
],
|
|
]),
|
|
]);
|
|
|
|
$data1 = $this->db->select('test', [], ['id' => 1])->fetch();
|
|
$data2 = $this->db->select('test', [], ['id' => 2])->fetch();
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('test', ['id' => 1], ['id' => 1]),
|
|
reason: 'Should throw TypeError exception if cols value is not a string.',
|
|
exception: TypeError::class,
|
|
exceptionMessage: 'must be of type string',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('test', ['*'], ['id'], options: ['count' => true]),
|
|
reason: 'Should throw exception if conditions parameter is not an key/value pairs.',
|
|
exception: TypeError::class,
|
|
exceptionMessage: 'must be of type string',
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data1,
|
|
$this->db->select('test', [], ['id' => 1])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], ['id' => 2])->fetch(),
|
|
'Should return the record with id 2.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], ['id' => 2], options: [
|
|
'orderby' => ['id' => 'DESC'],
|
|
'limit' => 1,
|
|
'start' => 0,
|
|
'groupby' => ['id'],
|
|
])->fetch(),
|
|
'Should return the record with id 2.'
|
|
);
|
|
}
|
|
|
|
public function test_lock_retry()
|
|
{
|
|
/** @noinspection PhpUnhandledExceptionInspection */
|
|
$random = random_int(1, 100);
|
|
|
|
$this->db->transactional(function (DBLayer $db, array $options = []) use ($random) {
|
|
// -- trigger database lock exception
|
|
if ((int)ag($options, 'attempts', 0) < 1) {
|
|
throw new PDOException('database is locked');
|
|
}
|
|
|
|
$db->insert('sqlite_sequence', ['name' => 'test-' . $random, 'seq' => 1]);
|
|
}, options: [
|
|
'max_sleep' => 0,
|
|
]);
|
|
|
|
$this->assertSame(1, $this->db->getCount('sqlite_sequence', ['name' => 'test-' . $random]));
|
|
|
|
$this->checkException(
|
|
closure: function () use ($random) {
|
|
$this->db->transactional(fn() => throw new PDOException('database is locked'), options: [
|
|
'max_sleep' => 0,
|
|
'max_attempts' => 1,
|
|
]);
|
|
},
|
|
reason: 'Should throw an exception when the maximum number of attempts is reached.',
|
|
exception: DBLayerException::class,
|
|
exceptionMessage: 'database is locked',
|
|
);
|
|
$this->checkException(
|
|
closure: function () use ($random) {
|
|
$this->db->transactional(fn() => throw new PDOException('database is locked'), options: [
|
|
'max_sleep' => 0,
|
|
'max_attempts' => 1,
|
|
'on_lock' => fn() => throw new DBLayerException('on_lock called'),
|
|
]);
|
|
},
|
|
reason: 'Should throw an exception when the maximum number of attempts is reached.',
|
|
exception: DBLayerException::class,
|
|
exceptionMessage: 'on_lock called',
|
|
);
|
|
}
|
|
|
|
public function test_condition_parser()
|
|
{
|
|
$this->db->insert('test', [
|
|
'name' => 'test',
|
|
'watched' => 1,
|
|
'added_at' => 1,
|
|
'updated_at' => 2,
|
|
'json_data' => json_encode([
|
|
'my_id' => 1,
|
|
'my_name' => 'test',
|
|
'my_data' => [
|
|
'my_id' => 1,
|
|
'my_name' => 'test',
|
|
],
|
|
]),
|
|
]);
|
|
|
|
$this->db->insert('test', [
|
|
'name' => 'test2',
|
|
'watched' => 0,
|
|
'added_at' => 3,
|
|
'updated_at' => 4,
|
|
'json_data' => json_encode([
|
|
'my_id' => 2,
|
|
'my_name' => 'test2',
|
|
'my_data' => [
|
|
'my_id' => 2,
|
|
'my_name' => 'test2',
|
|
],
|
|
]),
|
|
]);
|
|
|
|
|
|
$data1 = $this->db->select('test', [], ['id' => 1])->fetch();
|
|
$data2 = $this->db->select('test', [], ['id' => 2])->fetch();
|
|
|
|
$this->db->insert('fts_table', ['name' => $data1['name'], 'json_data' => $data1['json_data']]);
|
|
$this->db->insert('fts_table', ['name' => $data2['name'], 'json_data' => $data2['json_data']]);
|
|
|
|
$this->assertSame(
|
|
$data1,
|
|
$this->db->select('test', [], [
|
|
'id' => [DBLayer::IS_LOWER_THAN_OR_EQUAL, 1],
|
|
])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], [
|
|
'id' => [DBLayer::IS_HIGHER_THAN_OR_EQUAL, 2],
|
|
])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
$this->assertSame(
|
|
$data1,
|
|
$this->db->select('test', [], [
|
|
'added_at' => [DBLayer::IS_BETWEEN, [1, 2]],
|
|
])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], [
|
|
'added_at' => [DBLayer::IS_NOT_BETWEEN, [1, 2]],
|
|
])->fetch(),
|
|
'Should return the record with id 2.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data1,
|
|
$this->db->select('test', [], [
|
|
'nullable' => [DBLayer::IS_NULL],
|
|
])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], [
|
|
'name' => [DBLayer::IS_LIKE, 'test2'],
|
|
])->fetch(),
|
|
'Should return the record with id 2.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data1,
|
|
$this->db->select('test', [], [
|
|
'name' => [DBLayer::IS_NOT_LIKE, 'test2'],
|
|
])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
$this->assertSame(
|
|
$data1,
|
|
$this->db->select('test', [], [
|
|
'id' => [DBLayer::IS_IN, [0, 1]],
|
|
])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], [
|
|
'id' => [DBLayer::IS_NOT_IN, [0, 1]],
|
|
])->fetch(),
|
|
'Should return the record with id 2.'
|
|
);
|
|
|
|
try {
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], ['json_data' => [DBLayer::IS_JSON_CONTAINS, '$.my_id', 2],])->fetch(),
|
|
'Should return the record with id 1.'
|
|
);
|
|
} catch (DBLayerException $e) {
|
|
if (str_contains($e->getMessage(), 'no such function') && 'sqlite' === $this->db->getDriver()) {
|
|
// -- pass as sqlite does not support json_contains
|
|
} else {
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('test', [], ['json_data' => [DBLayer::IS_JSON_CONTAINS, '$.my_id'],]
|
|
)->fetch(),
|
|
reason: 'Should throw an exception when json_contains receives less then expected parameters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'IS_JSON_CONTAINS: expects 2',
|
|
);
|
|
|
|
$this->assertSame(
|
|
$data2,
|
|
$this->db->select('test', [], ['json_data' => [DBLayer::IS_JSON_EXTRACT, '$.my_id', '>', 1]])->fetch(),
|
|
'Should return the record with id 2.'
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('test', [], ['json_data' => [DBLayer::IS_JSON_EXTRACT, '$.my_id', '>']]),
|
|
reason: 'Should throw an exception when json_extract receives less then expected parameters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'IS_JSON_EXTRACT: expects 3',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('test', [], ['json_data' => ['NOT_SET', '$.my_id', '>']]),
|
|
reason: 'Should throw exception on unknown operator.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'expr not implemented',
|
|
);
|
|
|
|
$this->assertSame(
|
|
'test',
|
|
$this->db->select('fts_table', [], [
|
|
'name' => [DBLayer::IS_MATCH_AGAINST, ['name'], 'test'],
|
|
])->fetch()['name'],
|
|
'Should return the record with id 2.'
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('fts_table', [], [
|
|
'name' => [DBLayer::IS_MATCH_AGAINST, ['name']],
|
|
])->fetch(),
|
|
reason: 'Should throw an exception when match against receives less then expected parameters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'IS_MATCH_AGAINST: expects 2',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('fts_table', [], [
|
|
'name' => [DBLayer::IS_MATCH_AGAINST, 'name', 'test'],
|
|
])->fetch(),
|
|
reason: 'Should throw an exception when match against receives less then expected parameters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'IS_MATCH_AGAINST: expects parameter 1 to be array',
|
|
);
|
|
|
|
$this->checkException(
|
|
closure: fn() => $this->db->select('fts_table', [], [
|
|
'name' => [DBLayer::IS_MATCH_AGAINST, ['name'], ['test']],
|
|
])->fetch(),
|
|
reason: 'Should throw an exception when match against receives less then expected parameters.',
|
|
exception: RuntimeException::class,
|
|
exceptionMessage: 'IS_MATCH_AGAINST: expects parameter 2 to be string',
|
|
);
|
|
}
|
|
|
|
}
|