Version 3 #1

Manually merged
timw4mail merged 47 commits from develop into master 2020-04-23 18:39:27 -04:00
11 changed files with 342 additions and 272 deletions
Showing only changes of commit 77df254a0c - Show all commits

View File

@ -1,4 +1,7 @@
<?php declare(strict_types=1);
use Robo\Tasks;
if ( ! function_exists('glob_recursive'))
{
// Does not support flag GLOB_BRACE
@ -20,7 +23,7 @@ if ( ! function_exists('glob_recursive'))
*
* @see http://robo.li/
*/
class RoboFile extends \Robo\Tasks {
class RoboFile extends Tasks {
/**
* Directories used by analysis tools
@ -51,20 +54,20 @@ class RoboFile extends \Robo\Tasks {
/**
* Do static analysis tasks
*/
public function analyze()
public function analyze(): void
{
$this->prepare();
$this->lint();
$this->phploc(TRUE);
$this->phpcs(TRUE);
$this->dependencyReport();
$this->phpmd(TRUE);
$this->phpcpdReport();
}
/**
* Run all tests, generate coverage, generate docs, generate code statistics
*/
public function build()
public function build(): void
{
$this->analyze();
$this->coverage();
@ -74,11 +77,11 @@ class RoboFile extends \Robo\Tasks {
/**
* Cleanup temporary files
*/
public function clean()
public function clean(): void
{
// So the task doesn't complain,
// make any 'missing' dirs to cleanup
array_map(function ($dir) {
array_map(static function ($dir) {
if ( ! is_dir($dir))
{
`mkdir -p {$dir}`;
@ -92,19 +95,15 @@ class RoboFile extends \Robo\Tasks {
/**
* Run unit tests and generate coverage reports
*/
public function coverage()
public function coverage(): void
{
$this->taskPhpUnit()
->configFile('build/phpunit.xml')
->run();
// $this->_run(['phpdbg -qrr -- vendor/bin/phpunit -c build']);
$this->_run(['phpdbg -qrr -- vendor/bin/phpunit -c build']);
}
/**
* Generate documentation with phpdox
*/
public function docs()
public function docs(): void
{
$this->_run(['vendor/bin/phpdox']);
}
@ -112,11 +111,11 @@ class RoboFile extends \Robo\Tasks {
/**
* Verify that source files are valid
*/
public function lint()
public function lint(): void
{
$files = $this->getAllSourceFiles();
$chunks = array_chunk($files, (int)`getconf _NPROCESSORS_ONLN`);
$chunks = array_chunk($files, (int)shell_exec('getconf _NPROCESSORS_ONLN'));
foreach($chunks as $chunk)
{
@ -129,7 +128,7 @@ class RoboFile extends \Robo\Tasks {
*
* @param bool $report - if true, generates reports instead of direct output
*/
public function phpcs($report = FALSE)
public function phpcs($report = FALSE): void
{
$dir = __DIR__;
@ -149,12 +148,36 @@ class RoboFile extends \Robo\Tasks {
$this->_run($cmd_parts);
}
public function phpmd($report = FALSE): void
{
$report_cmd_parts = [
'vendor/bin/phpmd',
'./src',
'xml',
'cleancode,codesize,controversial,design,naming,unusedcode',
'--exclude ParallelAPIRequest',
'--reportfile ./build/logs/phpmd.xml'
];
$normal_cmd_parts = [
'vendor/bin/phpmd',
'./src',
'ansi',
'cleancode,codesize,controversial,design,naming,unusedcode',
'--exclude ParallelAPIRequest'
];
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
$this->_run($cmd_parts);
}
/**
* Run the phploc tool
*
* @param bool $report - if true, generates reports instead of direct output
*/
public function phploc($report = FALSE)
public function phploc($report = FALSE): void
{
// Command for generating reports
$report_cmd_parts = [
@ -182,7 +205,7 @@ class RoboFile extends \Robo\Tasks {
/**
* Create temporary directories
*/
public function prepare()
public function prepare(): void
{
array_map([$this, '_mkdir'], $this->taskDirs);
}
@ -190,7 +213,7 @@ class RoboFile extends \Robo\Tasks {
/**
* Lint php files and run unit tests
*/
public function test()
public function test(): void
{
$this->lint();
$this->taskPhpUnit()
@ -202,7 +225,7 @@ class RoboFile extends \Robo\Tasks {
/**
* Watches for file updates, and automatically runs appropriate actions
*/
public function watch()
public function watch(): void
{
$this->taskWatch()
->monitor('composer.json', function() {
@ -217,27 +240,12 @@ class RoboFile extends \Robo\Tasks {
->run();
}
/**
* Create pdepend reports
*/
protected function dependencyReport()
{
$cmd_parts = [
'vendor/bin/pdepend',
'--jdepend-xml=build/logs/jdepend.xml',
'--jdepend-chart=build/pdepend/dependencies.svg',
'--overview-pyramid=build/pdepend/overview-pyramid.svg',
'src'
];
$this->_run($cmd_parts);
}
/**
* Get the total list of source files, including tests
*
* @return array
*/
protected function getAllSourceFiles()
protected function getAllSourceFiles(): array
{
$files = array_merge(
glob_recursive('build/*.php'),
@ -256,7 +264,7 @@ class RoboFile extends \Robo\Tasks {
*
* @param array $chunk
*/
protected function parallelLint(array $chunk)
protected function parallelLint(array $chunk): void
{
$task = $this->taskParallelExec()
->timeout(5)
@ -273,7 +281,7 @@ class RoboFile extends \Robo\Tasks {
/**
* Generate copy paste detector report
*/
protected function phpcpdReport()
protected function phpcpdReport(): void
{
$cmd_parts = [
'vendor/bin/phpcpd',
@ -290,7 +298,7 @@ class RoboFile extends \Robo\Tasks {
* @param array $cmd_parts - command arguments
* @param string $join_on - what to join the command arguments with
*/
protected function _run(array $cmd_parts, $join_on = ' ')
protected function _run(array $cmd_parts, $join_on = ' '): void
{
$this->taskExec(implode($join_on, $cmd_parts))->run();
}

View File

@ -3,7 +3,7 @@
*
* SQL Query Builder / Database Abstraction Layer
*
* PHP version 7.2
* PHP version 7.3
*
* @package Query
* @author Timothy J. Warren <tim@timshomepage.net>

View File

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
beStrictAboutOutputDuringTests="false"
colors="true"
stopOnFailure="false"
bootstrap="./../tests/bootstrap.php">
bootstrap="./../tests/bootstrap.php"
verbose="true"
>
<filter>
<whitelist>
<directory suffix=".php">./../src/</directory>

View File

@ -1,8 +1,8 @@
{
"name":"aviat/query",
"type":"library",
"description":"Database Query Builder and Abstraction layer",
"keywords":[
"name": "aviat/query",
"type": "library",
"description": "Database Query Builder and Abstraction layer",
"keywords": [
"database",
"query builder",
"codeigniter",
@ -11,35 +11,45 @@
"postgres",
"pdo"
],
"homepage":"https://git.timshomepage.net/aviat/Query",
"license":"MIT",
"authors": [{
"homepage": "https://git.timshomepage.net/aviat/Query",
"license": "MIT",
"authors": [
{
"name": "Timothy J. Warren",
"email": "tim@timshomepage.net",
"homepage": "https://timshomepage.net",
"role": "Developer"
}],
}
],
"config": {
"lock": false,
"platform": {
"php": "7.3"
}
},
"require": {
"php": "^7.2",
"php": ">=7.3",
"ext-pdo": "*"
},
"require-dev": {
"consolidation/robo": "^2.0.0",
"monolog/monolog": "^2.0.1",
"pdepend/pdepend": "^2.5",
"phploc/phploc": "^5.0",
"phpmd/phpmd": "^2.8",
"phpstan/phpstan": "^0.12.2",
"phpunit/phpunit": "^8.5",
"sebastian/phpcpd": "^4.1",
"simpletest/simpletest": "^1.1",
"squizlabs/php_codesniffer": "^3.0.0",
"theseer/phpdox": "^0.12.0"
"theseer/phpdox": "*"
},
"autoload": {
"psr-4": {
"Query\\": "src"
},
"files": ["src/common.php"]
"files": [
"src/common.php"
]
},
"autoload-dev": {
"psr-4": {
@ -50,6 +60,7 @@
"build": "robo build",
"clean": "robo clean",
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build",
"pcov": "vendor/bin/phpunit -c build",
"phpstan": "phpstan analyse -l 3 -c phpstan.neon src tests",
"test": "phpunit -c build --no-coverage"
},

View File

@ -78,24 +78,20 @@
</source>
<!-- git vcs information -->
<source type="git">
<!-- <source type="git">
<git binary="/usr/bin/git" />
<history enabled="true" limit="15" cache="${phpDox.project.workdir}/gitlog.xml" />
</source>
</source> -->
<!-- PHP Code Sniffer findings -->
<!--
<source type="phpcs">
<source type="checkstyle">
<file name="phpcs.xml" />
</source>
-->
<!-- PHPMessDetector -->
<!--
<source type="pmd">
<file name="pmd.xml" />
<file name="phpmd.xml" />
</source>
-->
<!-- PHPUnit Coverage XML -->
<!--

View File

@ -15,6 +15,7 @@
*/
namespace Query;
use function is_array;
use function regexInArray;
use BadMethodCallException;
@ -46,6 +47,7 @@ use Query\Drivers\DriverInterface;
* @method getTriggers(): array | null
* @method getTypes(): array | null
* @method getUtil(): \Query\Drivers\AbstractUtil
* @method getVersion(): string
* @method getViews(): array | null
* @method inTransaction(): bool
* @method lastInsertId(string $name = NULL): string
@ -78,10 +80,16 @@ class QueryBuilder implements QueryBuilderInterface {
/**
* Whether to do only an explain on the query
* @var boolean
* @var bool
*/
protected $explain = FALSE;
/**
* Whether to return data from a modification query
* @var bool
*/
protected $returning = FALSE;
/**
* The current database driver
* @var DriverInterface
@ -142,7 +150,7 @@ class QueryBuilder implements QueryBuilderInterface {
{
if (method_exists($this->driver, $name))
{
return \call_user_func_array([$this->driver, $name], $params);
return $this->driver->$name(...$params);
}
throw new BadMethodCallException('Method does not exist');
@ -180,11 +188,11 @@ class QueryBuilder implements QueryBuilderInterface {
unset($fieldsArray);
// Join the strings back together
for($i = 0, $c = count($safeArray); $i < $c; $i++)
foreach ($safeArray as $i => $iValue)
{
if (\is_array($safeArray[$i]))
if (is_array($iValue))
{
$safeArray[$i] = implode(' AS ', $safeArray[$i]);
$safeArray[$i] = implode(' AS ', $iValue);
}
}
@ -250,12 +258,21 @@ class QueryBuilder implements QueryBuilderInterface {
}
/**
* @todo implement
* Add a 'returning' clause to an insert,update, or delete query
*
* @param string $fields
* @return $this
*/
public function returning(string $fields = '*'): QueryBuilderInterface
public function returning(string $fields = ''): QueryBuilderInterface
{
$this->returning = TRUE;
// Re-use the string select field for generating the returning type clause
if ($fields !== '')
{
return $this->select($fields);
}
return $this;
}
@ -762,7 +779,7 @@ class QueryBuilder implements QueryBuilderInterface {
$this->from($table);
}
$result = $this->_run('get', $table, NULL, NULL, $reset);
$result = $this->_run(QueryType::SELECT, $table, NULL, NULL, $reset);
$rows = $result->fetchAll();
return (int) count($rows);
@ -782,7 +799,7 @@ class QueryBuilder implements QueryBuilderInterface {
$this->set($data);
}
return $this->_run('insert', $table);
return $this->_run(QueryType::INSERT, $table);
}
/**
@ -797,6 +814,8 @@ class QueryBuilder implements QueryBuilderInterface {
// Get the generated values and sql string
[$sql, $data] = $this->driver->insertBatch($table, $data);
return $sql !== NULL
? $this->_run('', $table, $sql, $data)
: NULL;
@ -816,7 +835,7 @@ class QueryBuilder implements QueryBuilderInterface {
$this->set($data);
}
return $this->_run('update', $table);
return $this->_run(QueryType::UPDATE, $table);
}
/**
@ -857,7 +876,7 @@ class QueryBuilder implements QueryBuilderInterface {
$this->where($where);
}
return $this->_run('delete', $table);
return $this->_run(QueryType::DELETE, $table);
}
// --------------------------------------------------------------------------
@ -879,7 +898,7 @@ class QueryBuilder implements QueryBuilderInterface {
$this->from($table);
}
return $this->_getCompile('select', $table, $reset);
return $this->_getCompile(QueryType::SELECT, $table, $reset);
}
/**
@ -891,7 +910,7 @@ class QueryBuilder implements QueryBuilderInterface {
*/
public function getCompiledInsert(string $table, bool $reset=TRUE): string
{
return $this->_getCompile('insert', $table, $reset);
return $this->_getCompile(QueryType::INSERT, $table, $reset);
}
/**
@ -903,7 +922,7 @@ class QueryBuilder implements QueryBuilderInterface {
*/
public function getCompiledUpdate(string $table='', bool $reset=TRUE): string
{
return $this->_getCompile('update', $table, $reset);
return $this->_getCompile(QueryType::UPDATE, $table, $reset);
}
/**
@ -915,7 +934,7 @@ class QueryBuilder implements QueryBuilderInterface {
*/
public function getCompiledDelete(string $table='', bool $reset=TRUE): string
{
return $this->_getCompile('delete', $table, $reset);
return $this->_getCompile(QueryType::DELETE, $table, $reset);
}
// --------------------------------------------------------------------------
@ -931,6 +950,7 @@ class QueryBuilder implements QueryBuilderInterface {
{
$this->state = new State();
$this->explain = FALSE;
$this->returning = FALSE;
}
/**
@ -1204,7 +1224,7 @@ class QueryBuilder implements QueryBuilderInterface {
*/
protected function _appendQuery(array $values, string $sql, int $totalTime): void
{
$evals = \is_array($values) ? $values : [];
$evals = is_array($values) ? $values : [];
$esql = str_replace('?', '%s', $sql);
// Quote string values
@ -1240,12 +1260,12 @@ class QueryBuilder implements QueryBuilderInterface {
* @param string $table
* @return string
*/
protected function _compileType(string $type='', string $table=''): string
protected function _compileType(string $type=QueryType::SELECT, string $table=''): string
{
$setArrayKeys = $this->state->getSetArrayKeys();
switch($type)
{
case 'insert':
case QueryType::INSERT:
$paramCount = count($setArrayKeys);
$params = array_fill(0, $paramCount, '?');
$sql = "INSERT INTO {$table} ("
@ -1253,16 +1273,16 @@ class QueryBuilder implements QueryBuilderInterface {
. ")\nVALUES (".implode(',', $params).')';
break;
case 'update':
case QueryType::UPDATE:
$setString = $this->state->getSetString();
$sql = "UPDATE {$table}\nSET {$setString}";
break;
case 'delete':
case QueryType::DELETE:
$sql = "DELETE FROM {$table}";
break;
// Get queries
case QueryType::SELECT:
default:
$fromString = $this->state->getFromString();
$selectString = $this->state->getSelectString();
@ -1305,7 +1325,7 @@ class QueryBuilder implements QueryBuilderInterface {
{
$func = 'get' . ucFirst($clause);
$param = $this->state->$func();
if (\is_array($param))
if (is_array($param))
{
foreach($param as $q)
{
@ -1325,6 +1345,9 @@ class QueryBuilder implements QueryBuilderInterface {
$sql = $this->driver->getSql()->limit($sql, $limit, $this->state->getOffset());
}
// Set the returning clause, if applicable
$sql = $this->_compileReturning($sql, $type);
// See if the query plan, rather than the
// query data should be returned
if ($this->explain === TRUE)
@ -1334,4 +1357,51 @@ class QueryBuilder implements QueryBuilderInterface {
return $sql;
}
/**
* Generate returning clause of query
*
* @param string $sql
* @param string $type
* @return string
*/
protected function _compileReturning(string $sql, string $type): string
{
if ($this->returning === FALSE)
{
return $sql;
}
$rawSelect = $this->state->getSelectString();
$selectString = ($rawSelect === '') ? '*' : $rawSelect;
$returningSQL = $this->driver->returning($sql, $selectString);
if ($returningSQL === $sql)
{
// If the driver doesn't support the returning clause, it returns the original query.
// Fake the same result with a transaction and a select query
if ( ! $this->inTransaction())
{
$this->beginTransaction();
}
// Generate the appropriate select query for the returning clause fallback
switch ($type)
{
case QueryType::INSERT:
// @TODO figure out a good response for insert query
break;
case QueryType::UPDATE:
// @TODO figure out a good response for update query
break;
case QueryType::DELETE:
// @TODO Figure out a good response for delete query
break;
}
}
return $returningSQL;
}
}

View File

@ -42,6 +42,7 @@ use PDOStatement;
* @method getTriggers(): array | null
* @method getTypes(): array | null
* @method getUtil(): \Query\Drivers\AbstractUtil
* @method getVersion(): string
* @method getViews(): array | null
* @method inTransaction(): bool
* @method lastInsertId(string $name = NULL): string
@ -106,6 +107,14 @@ interface QueryBuilderInterface {
*/
public function selectSum(string $field, $as=FALSE): self;
/**
* Add a 'returning' clause to an insert,update, or delete query
*
* @param string $fields
* @return self
*/
public function returning(string $fields = '*'): self;
/**
* Adds the 'distinct' keyword to a query
*

View File

@ -17,6 +17,25 @@ namespace Query;
/**
* Query builder state
*
* @method getSelectString(): string
* @method getFromString(): string
* @method getSetString(): string
* @method getOrderString(): string
* @method getGroupString(): string
* @method getSetArrayKeys(): array
* @method getOrderArray(): array
* @method getGroupArray(): array
* @method getValues(): array
* @method getWhereValues(): array
* @method getLimit(): int
* @method getOffset()
* @method getQueryMap(): array
* @method getHavingMap(): array
*
* @method setSelectString(string): self
* @method setFromString(string): self
* @method setSetString(string): self
*/
class State {
// --------------------------------------------------------------------------
@ -124,22 +143,28 @@ class State {
*/
protected $havingMap = [];
/**
* @param string $str
* @return State
*/
public function setSelectString(string $str): self
public function __call(string $name, array $arguments)
{
$this->selectString = $str;
return $this;
if (strpos($name, 'get', 0) === 0)
{
$maybeProp = lcfirst(substr($name, 3));
if (isset($this->$maybeProp))
{
return $this->$maybeProp;
}
}
/**
* @return string
*/
public function getSelectString(): string
if (strpos($name, 'set', 0) === 0)
{
return $this->selectString;
$maybeProp = lcfirst(substr($name, 3));
if (isset($this->$maybeProp))
{
$this->$maybeProp = $arguments[0];
return $this;
}
}
return NULL;
}
/**
@ -152,50 +177,6 @@ class State {
return $this;
}
/**
* @return string
*/
public function getFromString(): string
{
return $this->fromString;
}
/**
* @param string $fromString
* @return State
*/
public function setFromString(string $fromString): self
{
$this->fromString = $fromString;
return $this;
}
/**
* @return string
*/
public function getSetString(): string
{
return $this->setString;
}
/**
* @param string $setString
* @return State
*/
public function setSetString(string $setString): self
{
$this->setString = $setString;
return $this;
}
/**
* @return string
*/
public function getOrderString(): string
{
return $this->orderString;
}
/**
* @param string $orderString
* @return State
@ -206,14 +187,6 @@ class State {
return $this;
}
/**
* @return string
*/
public function getGroupString(): string
{
return $this->groupString;
}
/**
* @param string $groupString
* @return State
@ -224,14 +197,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getSetArrayKeys(): array
{
return $this->setArrayKeys;
}
/**
* @param array $setArrayKeys
* @return State
@ -252,14 +217,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getOrderArray(): array
{
return $this->orderArray;
}
/**
* @param string $key
* @param mixed $orderArray
@ -271,14 +228,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getGroupArray(): array
{
return $this->groupArray;
}
/**
* @param array $groupArray
* @return State
@ -299,14 +248,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getValues(): array
{
return $this->values;
}
/**
* @param array $values
* @return State
@ -317,14 +258,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getWhereValues(): array
{
return $this->whereValues;
}
/**
* @param mixed $val
* @return State
@ -345,14 +278,6 @@ class State {
return $this;
}
/**
* @return int
*/
public function getLimit(): ?int
{
return $this->limit;
}
/**
* @param int $limit
* @return State
@ -363,14 +288,6 @@ class State {
return $this;
}
/**
* @return string|false
*/
public function getOffset()
{
return $this->offset;
}
/**
* @param string|false $offset
* @return State
@ -381,14 +298,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getQueryMap(): array
{
return $this->queryMap;
}
/**
* Add an additional set of mapping pairs to a internal map
*
@ -407,14 +316,6 @@ class State {
return $this;
}
/**
* @return array
*/
public function getHavingMap(): array
{
return $this->havingMap;
}
/**
* @param array $item
* @return State

View File

@ -557,6 +557,24 @@ abstract class BaseQueryBuilderTest extends TestCase {
$this->assertIsA($query, 'PDOStatement');
}
public function testInsertReturning(): void
{
$query = self::$db->set('id', 99)
->set('key', 84)
->set('val', 120)
->returning()
->insert('test');
$row = $query->fetch(PDO::FETCH_ASSOC);
$this->assertIsA($query, 'PDOStatement');
$this->assertEqual([
'id' => 99,
'key' => 84,
'val' => 120,
], $row);
}
public function testInsertBatch(): void
{
$data = [
@ -594,6 +612,33 @@ abstract class BaseQueryBuilderTest extends TestCase {
$this->assertIsA($query, 'PDOStatement');
}
public function testUpdateReturning(): void
{
// Make sure the id exists so there isn't an error for that reason
self::$db->insert('test', [
'id' => 7,
'key' => 'kjsgarik yugasdikh',
'val' => 'alkfiasghdfdhs',
]);
$query = self::$db->where('id', 7)
->set([
'id' => 7,
'key' => 'gogle',
'val' => 'non-word'
])
->returning('key')
->update('test');
$this->assertIsA($query, 'PDOStatement');
$row = $query->fetch(PDO::FETCH_ASSOC);
$this->assertEqual([
'key' => 'gogle',
], $row, json_encode($query->errorInfo()));
// $this->assertNotEqual(FALSE, $row, $query->errorInfo());
}
public function testUpdateBatchNull(): void
{
$query = self::$db->updateBatch('test', [], '');
@ -652,6 +697,13 @@ abstract class BaseQueryBuilderTest extends TestCase {
$this->assertIsA($query, 'PDOStatement');
}
public function testDeleteReturning():void
{
$query = self::$db->returning()->delete('test', ['id' => 99]);
$this->assertIsA($query, 'PDOStatement');
}
public function testDeleteWithMultipleWhereValues(): void
{
$query = self::$db->delete('test', [

View File

@ -8,7 +8,7 @@
*
* @package Query
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2012 - 2020 Timothy J. Warren
* @copyright 2012 - 2019 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://git.timshomepage.net/aviat/Query
* @version 3.0.0
@ -72,4 +72,14 @@ class MySQLQueryBuilderTest extends BaseQueryBuilderTest {
$this->assertTrue(count(array_keys($res[0])) > 1);
$this->assertTrue(array_key_exists('table', $res[0]));
}
public function testInsertReturning(): void
{
$this->markTestSkipped();
}
public function testUpdateReturning(): void
{
$this->markTestSkipped();
}
}

View File

@ -8,7 +8,7 @@
*
* @package Query
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2012 - 2020 Timothy J. Warren
* @copyright 2012 - 2019 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://git.timshomepage.net/aviat/Query
* @version 3.0.0
@ -113,4 +113,14 @@ use Query\Tests\BaseQueryBuilderTest;
$this->assertTrue(FALSE);
}
}
public function testInsertReturning(): void
{
$this->markTestSkipped();
}
public function testUpdateReturning(): void
{
$this->markTestSkipped();
}
}