Compare commits

...

5 Commits

Author SHA1 Message Date
Clemens Schwaighofer
37367db878 Fix regex for $$ PostgresSQL string in convert placeholder 2025-04-07 19:44:18 +09:00
Clemens Schwaighofer
2d30d1d160 Rewrite DB param lookup
* Correct wrong comment lookup
* simplify regex by excluding comment and string blocks before
* simpler lookup for each type
* update checks for more tests for various special cases

In DB IO
* add a function to return all placeholders found in a query
* only numbered parameters are looked up
2025-04-07 17:30:30 +09:00
Clemens Schwaighofer
531229e8b7 Add DB Encryption tests 2025-04-07 12:05:06 +09:00
Clemens Schwaighofer
d09c20ff9d hash test page update 2025-04-07 09:09:45 +09:00
Clemens Schwaighofer
f4ddc5a5fc Add hash hmac to the Create Hash class 2025-04-07 09:05:37 +09:00
11 changed files with 587 additions and 144 deletions

View File

@@ -21,8 +21,10 @@ final class CoreLibsCreateHashTest extends TestCase
public function hashData(): array public function hashData(): array
{ {
return [ return [
'any string' => [ 'hash tests' => [
// this is the string
'text' => 'Some String Text', 'text' => 'Some String Text',
// hash list special
'crc32b_reverse' => 'c5c21d91', // crc32b (in revere) 'crc32b_reverse' => 'c5c21d91', // crc32b (in revere)
'sha1Short' => '4d2bc9ba0', // sha1Short 'sha1Short' => '4d2bc9ba0', // sha1Short
// via hash // via hash
@@ -31,6 +33,8 @@ final class CoreLibsCreateHashTest extends TestCase
'fnv132' => '9df444f9', // hash: fnv132 'fnv132' => '9df444f9', // hash: fnv132
'fnv1a32' => '2c5f91b9', // hash: fnv1a32 'fnv1a32' => '2c5f91b9', // hash: fnv1a32
'joaat' => '50dab846', // hash: joaat 'joaat' => '50dab846', // hash: joaat
'ripemd160' => 'aeae3f041b20136451519edd9361570909300342', // hash: ripemd160,
'sha256' => '9055080e022f224fa835929b80582b3c71c672206fa3a49a87412c25d9d42ceb', // hash: sha256
] ]
]; ];
} }
@@ -81,7 +85,7 @@ final class CoreLibsCreateHashTest extends TestCase
{ {
$list = []; $list = [];
foreach ($this->hashData() as $name => $values) { foreach ($this->hashData() as $name => $values) {
foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat'] as $_hash_type) { foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256'] as $_hash_type) {
// default value test // default value test
if ($_hash_type === null) { if ($_hash_type === null) {
$hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT; $hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT;
@@ -288,7 +292,7 @@ final class CoreLibsCreateHashTest extends TestCase
* Undocumented function * Undocumented function
* *
* @covers ::hash * @covers ::hash
* @testdox hash with invalid type [$_dataName] * @testdox hash with invalid type
* *
* @return void * @return void
*/ */
@@ -301,6 +305,122 @@ final class CoreLibsCreateHashTest extends TestCase
\CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST') \CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST')
); );
} }
/**
* Note: this only tests default sha256
*
* @covers ::hashHmac
* @testdox hash hmac test
*
* @return void
*/
public function testHashMac(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = '16479b3ef6fa44e1cdd8b2dcfaadf314d1a7763635e8738f1e7996d714d9b6bf';
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key)
);
}
/**
* Undocumented function
*
* @covers ::hashHmac
* @testdox hash hmac with invalid type
*
* @return void
*/
public function testInvalidHashMacType(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = hash_hmac(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source, $hash_key);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key, 'DOES_NOT_EXIST')
);
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function providerHashTypes(): array
{
return [
'Hash crc32b' => [
'crc32b',
true,
false,
],
'Hash adler32' => [
'adler32',
true,
false,
],
'HAsh fnv132' => [
'fnv132',
true,
false,
],
'Hash fnv1a32' => [
'fnv1a32',
true,
false,
],
'Hash: joaat' => [
'joaat',
true,
false,
],
'Hash: ripemd160' => [
'ripemd160',
true,
true,
],
'Hash: sha256' => [
'sha256',
true,
true,
],
'Hash: invalid' => [
'invalid',
false,
false
]
];
}
/**
* Undocumented function
*
* @covers ::isValidHashType
* @covers ::isValidHashHmacType
* @dataProvider providerHashTypes
* @testdox check if $hash_type is valid for hash $hash_ok and hash hmac $hash_hmac_ok [$_dataName]
*
* @param string $hash_type
* @param bool $hash_ok
* @param bool $hash_hmac_ok
* @return void
*/
public function testIsValidHashAndHashHmacTypes(string $hash_type, bool $hash_ok, bool $hash_hmac_ok): void
{
$this->assertEquals(
$hash_ok,
\CoreLibs\Create\Hash::isValidHashType($hash_type),
'hash valid'
);
$this->assertEquals(
$hash_hmac_ok,
\CoreLibs\Create\Hash::isValidHashHmacType($hash_type),
'hash hmac valid'
);
}
} }
// __END__ // __END__

View File

@@ -135,6 +135,7 @@ final class CoreLibsDBIOTest extends TestCase
} }
// check if they already exist, drop them // check if they already exist, drop them
if ($db->dbShowTableMetaData('table_with_primary_key') !== false) { if ($db->dbShowTableMetaData('table_with_primary_key') !== false) {
$db->dbExec("CREATE EXTENSION IF NOT EXISTS pgcrypto");
$db->dbExec("DROP TABLE table_with_primary_key"); $db->dbExec("DROP TABLE table_with_primary_key");
$db->dbExec("DROP TABLE table_without_primary_key"); $db->dbExec("DROP TABLE table_without_primary_key");
$db->dbExec("DROP TABLE test_meta"); $db->dbExec("DROP TABLE test_meta");
@@ -4744,7 +4745,7 @@ final class CoreLibsDBIOTest extends TestCase
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string // all hast to be string
foreach ($res as $key => $value) { foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key); $this->assertIsString($value, 'Assert string for column: ' . $key);
} }
// convert base only // convert base only
$db->dbSetConvertFlag(Convert::on); $db->dbSetConvertFlag(Convert::on);
@@ -4757,10 +4758,10 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4774,13 +4775,13 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4794,17 +4795,17 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4818,25 +4819,25 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
case 'bytea': case 'bytea':
// for hex types it must not start with \x // for hex types it must not start with \x
$this->assertStringStartsNotWith( $this->assertStringStartsNotWith(
'\x', '\x',
$value, $value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name 'Assert bytes not starts with \x for column: ' . $key . '/' . $name
); );
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -5235,6 +5236,9 @@ final class CoreLibsDBIOTest extends TestCase
$3 $3
-- comment 3 -- comment 3
, $4 , $4
-- ignore $5, $6
-- $7, $8
-- digest($9, 10)
) )
SQL, SQL,
'count' => 4, 'count' => 4,
@@ -5305,8 +5309,57 @@ final class CoreLibsDBIOTest extends TestCase
SQL, SQL,
'count' => 2, 'count' => 2,
'convert' => false, 'convert' => false,
],
// special $$ string case
'text string, with $ placehoders that could be seen as $$ string' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_bytea = digest($3::VARCHAR, $4) OR
row_varchar = encode(digest($3, $4), 'hex') OR
row_bytea = hmac($3, $5, $4) OR
row_varchar = encode(hmac($3, $5, $4), 'hex') OR
row_bytea = pgp_sym_encrypt($3, $6) OR
row_varchar = encode(pgp_sym_encrypt($1, $6), 'hex') OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 6,
'convert' => false,
],
// NOTE, in SQL heredoc we cannot write $$ strings parts
'text string, with $ placehoders are in $$ strings' => [
'query' => '
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = $$some string$$ OR
row_varchar = $tag$some string$tag$ OR
row_varchar = $btag$some $1 string$btag$ OR
row_varchar = $btag$some $1 $subtag$ something $subtag$string$btag$ OR
row_varchar = $1
',
'count' => 1,
'convert' => false,
],
// a text string with escaped quite
'text string, with escaped quote' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = 'foo bar bar baz $5' OR
row_varchar = 'foo bar '' barbar $6' OR
row_varchar = E'foo bar \' barbar $7' OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 2,
'convert' => false,
] ]
]; ];
$string = <<<SQL
'''
SQL;
} }
/** /**

View File

@@ -21,6 +21,7 @@ ob_end_flush();
use CoreLibs\Debug\Support; use CoreLibs\Debug\Support;
use CoreLibs\DB\Support\ConvertPlaceholder; use CoreLibs\DB\Support\ConvertPlaceholder;
use CoreLibs\Convert\Html;
$log = new CoreLibs\Logging\Logging([ $log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG, 'log_folder' => BASE . LOG,
@@ -38,10 +39,12 @@ print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $log->getLogFile() . "<br>"; print "LOGFILE NAME: " . $log->getLogFile() . "<br>";
print "LOGFILE ID: " . $log->getLogFileId() . "<br>"; print "LOGFILE ID: " . $log->getLogFileId() . "<br>";
print "Lookup Regex: <pre>" . ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>"; print "Lookup Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_NAMED . "</pre>"; print "Lookup Numbered Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_NUMBERED) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK . "</pre>"; print "Replace Named Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NAMED) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_NUMBERED . "</pre>"; print "Replace Question Mark Regex: <pre>"
. Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK) . "</pre>";
print "Replace Numbered Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NUMBERED) . "</pre>";
$uniqid = \CoreLibs\Create\Uids::uniqIdShort(); $uniqid = \CoreLibs\Create\Uids::uniqIdShort();
// $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); // $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
@@ -91,40 +94,63 @@ RETURNING
some_binary some_binary
SQL; SQL;
print "[ALL] Convert: " print "<b>[ALL] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez";
$params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ']; $params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ'];
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez";
$params = null; $params = null;
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar"; $query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar";
$params = null; $params = null;
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key"; $query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key";
$params = null; $params = null;
print "[NO PARAMS] TEST: " print "<b>[NO PARAMS] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
print "[P-CONV]: " $query = <<<SQL
UPDATE table_with_primary_key SET
row_int = $1::INT, row_numeric = $1::NUMERIC, row_varchar = $1
WHERE
row_varchar = $1
SQL;
$params = [1];
print "<b>[All the same params] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>";
echo "<hr>";
$query = <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = :row_varchar
SQL;
$params = [':row_varchar' => 1];
print "<b>[: param] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>";
echo "<hr>";
print "<b>[P-CONV]</b>: "
. Support::printAr( . Support::printAr(
ConvertPlaceholder::updateParamList([ ConvertPlaceholder::updateParamList([
'original' => [ 'original' => [
@@ -186,6 +212,13 @@ SQL,
'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], 'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234],
'direction' => 'pg', 'direction' => 'pg',
], ],
'b?' => [
'query' => <<<SQL
SELECT test FROM test_foo = ?
SQL,
'params' => [1234],
'direction' => 'pg',
],
'b:' => [ 'b:' => [
'query' => <<<SQL 'query' => <<<SQL
INSERT INTO test_foo ( INSERT INTO test_foo (
@@ -220,7 +253,7 @@ foreach ($test_queries as $info => $data) {
$query = $data['query']; $query = $data['query'];
$params = $data['params']; $params = $data['params'];
$direction = $data['direction']; $direction = $data['direction'];
print "[$info] Convert: " print "<b>[$info] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";

View File

@@ -0,0 +1,125 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
// turn on all error reporting
error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', true);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-db-query-encryption';
ob_end_flush();
// use CoreLibs\Debug\Support;
use CoreLibs\Security\SymmetricEncryption;
use CoreLibs\Security\CreateKey;
use CoreLibs\Create\Hash;
use CoreLibs\Debug\Support;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// db connection and attach logger
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB QUERY ENCRYPTION';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title></head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
// encryption key
$key = CreateKey::generateRandomKey();
print "Secret Key: " . $key . "<br>";
// test text
$text_string = "I a some deep secret";
//
$crypt = new SymmetricEncryption($key);
$encrypted = $crypt->encrypt($text_string);
$string_hashed = Hash::hashStd($text_string);
$string_hmac = Hash::hashHmac($text_string, $key);
$decrypted = $crypt->decrypt($encrypted);
print "String: " . $text_string . "<br>";
print "Encrypted: " . $encrypted . "<br>";
print "Hashed: " . $string_hashed . "<br>";
print "Hmac: " . $string_hmac . "<br>";
$db->dbExecParams(
<<<SQL
INSERT INTO test_encryption (
-- for compare
plain_text,
-- via php encryption
hash_text, hmac_text, crypt_text,
-- -- in DB encryption
pg_digest_bytea, pg_digest_text,
pg_hmac_bytea, pg_hmac_text,
pg_crypt_bytea, pg_crypt_text
) VALUES (
$1,
$2, $3, $4,
digest($1::VARCHAR, $5),
encode(digest($1, $5), 'hex'),
hmac($1, $6, $5),
encode(hmac($1, $6, $5), 'hex'),
pgp_sym_encrypt($1, $7),
encode(pgp_sym_encrypt($1, $7), 'hex')
) RETURNING cuuid
SQL,
[
// 1: original string
$text_string,
// 2: hashed, 3: hmac, 4: encrypted
$string_hashed, $string_hmac, $encrypted,
// 5: hash type, 6: hmac secret, 7: pgp secret
'sha256', $key, $key
]
);
$cuuid = $db->dbGetReturningExt('cuuid');
print "INSERTED: " . print_r($cuuid, true) . "<br>";
print "LAST ERROR: " . $db->dbGetLastError(true) . "<br>";
// read back
$res = $db->dbReturnRowParams(
<<<SQL
SELECT
-- for compare
plain_text,
-- via php encryption
hash_text, hmac_text, crypt_text,
-- in DB encryption
pg_digest_bytea, pg_digest_text,
pg_hmac_bytea, pg_hmac_text,
pg_crypt_bytea, pg_crypt_text
FROM
test_encryption
WHERE
cuuid = $1
SQL,
[
$cuuid
]
);
print "RES: <pre>" . Support::prAr($res) . "</pre><br>";
// do compare
print "</body></html>";
// __END__

View File

@@ -54,7 +54,7 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
print "NO DB HANDLER<br>"; print "NO DB HANDLER<br>";
} }
// REGEX for placeholder count // REGEX for placeholder count
print "Placeholder regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>"; print "Placeholder lookup regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_NUMBERED . "</pre>";
// turn on debug replace for placeholders // turn on debug replace for placeholders
$db->dbSetDebugReplacePlaceholder(true); $db->dbSetDebugReplacePlaceholder(true);
@@ -148,6 +148,7 @@ RETURNING
bigint_a, number_real, number_double, numeric_3, bigint_a, number_real, number_double, numeric_3,
uuid_var uuid_var
SQL; SQL;
print "Placeholders: <pre>" . print_r($db->dbGetQueryParamPlaceholders($query_insert), true) . "<pre>";
$status = $db->dbExecParams($query_insert, $query_params); $status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>"; echo "<b>*</b><br>";
echo "INSERT ALL COLUMN TYPES: " echo "INSERT ALL COLUMN TYPES: "
@@ -326,6 +327,7 @@ SQL,
) { ) {
print "RES: " . Support::prAr($res) . "<br>"; print "RES: " . Support::prAr($res) . "<br>";
} }
print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "<br>";
print "ERROR: " . $db->dbGetLastError(true) . "<br>"; print "ERROR: " . $db->dbGetLastError(true) . "<br>";
print "</body></html>"; print "</body></html>";

View File

@@ -19,6 +19,7 @@ $LOG_FILE_ID = 'classTest-hash';
ob_end_flush(); ob_end_flush();
use CoreLibs\Create\Hash; use CoreLibs\Create\Hash;
use CoreLibs\Security\CreateKey;
$log = new CoreLibs\Logging\Logging([ $log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG, 'log_folder' => BASE . LOG,
@@ -43,16 +44,24 @@ print "S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>";
print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "<br>"; print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "<br>";
// print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "<br>"; // print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "<br>";
print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "<br>"; print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "<br>";
print "S::__hash(d): " . $to_crc . "/" // print "S::__hash(d): " . $to_crc . "/"
. Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "<br>"; // . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "<br>";
foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'sha512'] as $__hash_c) { $to_crc_list = [
print "S::__hash($__hash_c): $to_crc: " . $hash_class::__hash($to_crc, $__hash_c) . "<br>"; 'Some text block',
'Some String Text',
'any string',
];
foreach ($to_crc_list as $__to_crc) {
foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256', 'sha512'] as $__hash_c) {
print "Hash::hash($__hash_c): $__to_crc: " . Hash::hash($to_crc, $__hash_c) . "<br>";
}
} }
// static use // static use
print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>"; print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>";
echo "<hr>"; echo "<hr>";
$text = 'Some String Text'; $text = 'Some String Text';
// $text = 'any string';
$type = 'crc32b'; $type = 'crc32b';
print "Hash: " . $type . ": " . hash($type, $text) . "<br>"; print "Hash: " . $type . ": " . hash($type, $text) . "<br>";
// print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "<br>"; // print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "<br>";
@@ -66,6 +75,31 @@ print "HASH SHORT: " . $to_crc . ": " . Hash::hashShort($to_crc) . "<br>";
print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "<br>"; print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "<br>";
print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "<br>"; print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "<br>";
echo "<hr>";
$key = CreateKey::generateRandomKey();
$key = "FIX KEY";
print "Secret Key: " . $key . "<br>";
print "HASHMAC DEFAULT (fix): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "<br>";
$key = CreateKey::generateRandomKey();
print "Secret Key: " . $key . "<br>";
print "HASHMAC DEFAULT (random): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "<br>";
echo "<hr>";
$hash_types = ['crc32b', 'sha256', 'invalid'];
foreach ($hash_types as $hash_type) {
echo "<b>Checking $hash_type:</b><br>";
if (Hash::isValidHashType($hash_type)) {
echo "hash type: $hash_type is valid<br>";
} else {
echo "hash type: $hash_type is INVALID<br>";
}
if (Hash::isValidHashHmacType($hash_type)) {
echo "hash hmac type: $hash_type is valid<br>";
} else {
echo "hash hmac type: $hash_type is INVALID<br>";
}
}
// print "UNIQU ID SHORT : " . Hash::__uniqId() . "<br>"; // print "UNIQU ID SHORT : " . Hash::__uniqId() . "<br>";
// print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "<br>"; // print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "<br>";

View File

@@ -95,6 +95,7 @@ $test_files = [
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.db.single.php' => 'Class Test: DB single query tests', 'class_test.db.single.php' => 'Class Test: DB single query tests',
'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder',
'class_test.db.encryption.php' => 'Class Test: DB pgcrypto',
'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS',
'class_test.check.colors.php' => 'Class Test: CHECK COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS',
'class_test.mime.php' => 'Class Test: MIME', 'class_test.mime.php' => 'Class Test: MIME',

View File

@@ -49,7 +49,7 @@ class Hash
* replacement for __crc32b call * replacement for __crc32b call
* *
* @param string $string string to hash * @param string $string string to hash
* @param bool $use_sha use sha1 instead of crc32b (default false) * @param bool $use_sha [default=false] use sha1 instead of crc32b
* @return string hash of the string * @return string hash of the string
* @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true * @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true
*/ */
@@ -81,7 +81,7 @@ class Hash
* all that create 8 char long hashes * all that create 8 char long hashes
* *
* @param string $string string to hash * @param string $string string to hash
* @param string $hash_type hash type (default adler32) * @param string $hash_type [default=STANDARD_HASH_SHORT] hash type (default adler32)
* @return string hash of the string * @return string hash of the string
* @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types * @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types
*/ */
@@ -92,12 +92,40 @@ class Hash
return self::hash($string, $hash_type); return self::hash($string, $hash_type);
} }
/**
* check if hash type is valid, returns false if not
*
* @param string $hash_type
* @return bool
*/
public static function isValidHashType(string $hash_type): bool
{
if (!in_array($hash_type, hash_algos())) {
return false;
}
return true;
}
/**
* check if hash hmac type is valid, returns false if not
*
* @param string $hash_hmac_type
* @return bool
*/
public static function isValidHashHmacType(string $hash_hmac_type): bool
{
if (!in_array($hash_hmac_type, hash_hmac_algos())) {
return false;
}
return true;
}
/** /**
* creates a hash over string if any valid hash given. * creates a hash over string if any valid hash given.
* if no hash type set use sha256 * if no hash type set use sha256
* *
* @param string $string string to ash * @param string $string string to hash
* @param string $hash_type hash type (default sha256) * @param string $hash_type [default=STANDARD_HASH] hash type (default sha256)
* @return string hash of the string * @return string hash of the string
*/ */
public static function hash( public static function hash(
@@ -108,12 +136,36 @@ class Hash
empty($hash_type) || empty($hash_type) ||
!in_array($hash_type, hash_algos()) !in_array($hash_type, hash_algos())
) { ) {
// fallback to default hash type if none set or invalid // fallback to default hash type if empty or invalid
$hash_type = self::STANDARD_HASH; $hash_type = self::STANDARD_HASH;
} }
return hash($hash_type, $string); return hash($hash_type, $string);
} }
/**
* creates a hash mac key
*
* @param string $string string to hash mac
* @param string $key key to use
* @param string $hash_type [default=STANDARD_HASH]
* @return string hash mac string
*/
public static function hashHmac(
string $string,
#[\SensitiveParameter]
string $key,
string $hash_type = self::STANDARD_HASH
): string {
if (
empty($hash_type) ||
!in_array($hash_type, hash_hmac_algos())
) {
// fallback to default hash type if e or invalid
$hash_type = self::STANDARD_HASH;
}
return hash_hmac($hash_type, $string, $key);
}
/** /**
* short hash with max length of 8, uses adler32 * short hash with max length of 8, uses adler32
* *

View File

@@ -4283,6 +4283,17 @@ class IO
return $this->field_names[$pos] ?? false; return $this->field_names[$pos] ?? false;
} }
/**
* get all the $ placeholders
*
* @param string $query
* @return array<string>
*/
public function dbGetQueryParamPlaceholders(string $query): array
{
return $this->db_functions->__dbGetQueryParams($query);
}
/** /**
* Return a field type for a field name or pos, * Return a field type for a field name or pos,
* will return false if field is not found in list * will return false if field is not found in list

View File

@@ -978,12 +978,12 @@ class PgSQL implements Interface\SqlFunctions
} }
/** /**
* Count placeholder queries. $ only * Get the all the $ params, unique list
* *
* @param string $query * @param string $query
* @return int * @return array<string>
*/ */
public function __dbCountQueryParams(string $query): int public function __dbGetQueryParams(string $query): array
{ {
$matches = []; $matches = [];
// regex for params: only stand alone $number allowed // regex for params: only stand alone $number allowed
@@ -998,11 +998,22 @@ class PgSQL implements Interface\SqlFunctions
// Matches in 1:, must be array_filtered to remove empty, count with array_unique // Matches in 1:, must be array_filtered to remove empty, count with array_unique
// Regex located in the ConvertPlaceholder class // Regex located in the ConvertPlaceholder class
preg_match_all( preg_match_all(
ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS, ConvertPlaceholder::REGEX_LOOKUP_NUMBERED,
$query, $query,
$matches $matches
); );
return count(array_unique(array_filter($matches[3]))); return array_unique(array_filter($matches[ConvertPlaceholder::MATCHING_POS]));
}
/**
* Count placeholder queries. $ only
*
* @param string $query
* @return int
*/
public function __dbCountQueryParams(string $query): int
{
return count($this->__dbGetQueryParams($query));
} }
} }

View File

@@ -14,76 +14,57 @@ namespace CoreLibs\DB\Support;
class ConvertPlaceholder class ConvertPlaceholder
{ {
// NOTE for missing: range */+ are not iplemented in the regex below, but - is for now /** @var string text block in SQL, single quited
// NOTE some combinations are allowed, but the query will fail before this * Note that does not include $$..$$ strings or anything with token name or nested ones
/** @var string split regex, entries before $ group */ */
private const PATTERN_QUERY_SPLIT = private const PATTERN_TEXT_BLOCK_SINGLE_QUOTE = '(?:\'(?:[^\'\\\\]|\\\\.)*\')';
'\?\?|' // UNKNOWN: double ??, is this to avoid something? /** @var string text block in SQL, dollar quoted
. '[\(,]|' // for ',' and '(' mostly in INSERT or ANY() * NOTE: if this is added everything shifts by one lookup number
. '[<>=]|' // general set for <, >, = in any query with any combination */
. '\^@|' // text search for start from text with ^@ private const PATTERN_TEXT_BLOCK_DOLLAR = '(?:\$(\w*)\$.*?\$\1\$)';
. '\|\||' // concats two elements
. '&&|' // array overlap
. '\-\|\-|' // range overlap for array
. '[^-]-{1}|' // single -, used in JSON too
. '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-|' // JSON searches, Array searchs, etc
. 'THEN|ELSE' // command parts (CASE)
;
/** @var string the main regex including the pattern query split */
private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*';
/** @var string comment regex /** @var string comment regex
* anything that starts with -- and ends with a line break but any character that is not line break inbetween */ * anything that starts with -- and ends with a line break but any character that is not line break inbetween
private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)*\s*'; * this is the FIRST thing in the line and will skip any further lookups */
/** @var string parts to ignore in the SQL */ private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)';
private const PATTERN_IGNORE = // below are the params lookups
// digit -> ignore /** @var string named parameters, must start with single : */
'\d+|' private const PATTERN_NAMED = '((?<!:):(?:\w+))';
// other string -> ignore /** @var string question mark parameters, will catch any */
. '(?:\'.*?\')|'; private const PATTERN_QUESTION_MARK = '(\?{1})';
/** @var string named parameters */ /** @var string numbered parameters, can only start 1 to 9, second and further digits can be 0-9
private const PATTERN_NAMED = '(:\w+)'; * This ignores the $$ ... $$ escape syntax. If we find something like this will fail
/** @var string question mark parameters */ * It is recommended to use proper string escape quiting for writing data to the DB
private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))'; */
/** @var string numbered parameters */
private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)'; private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)';
// below here are full regex that will be used // below here are full regex that will be used
/** @var string replace regex for named (:...) entries */ /** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/' public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_NAMED . self::PATTERN_NAMED
. ')'
. '/s'; . '/s';
/** @var string replace regex for question mark (?) entries */ /** @var string replace regex for question mark (?) entries */
public const REGEX_REPLACE_QUESTION_MARK = '/' public const REGEX_REPLACE_QUESTION_MARK = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_QUESTION_MARK . self::PATTERN_QUESTION_MARK
. ')'
. '/s'; . '/s';
/** @var string replace regex for numbered ($n) entries */ /** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/' public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_NUMBERED . self::PATTERN_NUMBERED
. ')'
. '/s'; . '/s';
/** @var string the main lookup query for all placeholders */ /** @var string the main lookup query for all placeholders */
public const REGEX_LOOKUP_PLACEHOLDERS = '/' public const REGEX_LOOKUP_PLACEHOLDERS = '/'
// prefix string part, must match towards . self::PATTERN_COMMENT . '|'
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15] . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_ELEMENT . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_COMMENT
// match for replace part // match for replace part
. '(?:' . '(?:'
// ignore parts
. self::PATTERN_IGNORE
// :name named part (PDO) [1] // :name named part (PDO) [1]
. self::PATTERN_NAMED . '|' . self::PATTERN_NAMED . '|'
// ? question mark part (PDO) [2] // ? question mark part (PDO) [2]
@@ -94,6 +75,26 @@ class ConvertPlaceholder
. ')' . ')'
// single line -> add line break to matches in "." // single line -> add line break to matches in "."
. '/s'; . '/s';
/** @var string lookup for only numbered placeholders */
public const REGEX_LOOKUP_NUMBERED = '/'
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
// match for replace part
. '(?:'
// $n numbered part (\PG php) [1]
. self::PATTERN_NUMBERED
// end match
. ')'
. '/s';
/** @var int position for regex in full placeholder lookup: named */
public const LOOOKUP_NAMED_POS = 2;
/** @var int position for regex in full placeholder lookup: question mark */
public const LOOOKUP_QUESTION_MARK_POS = 3;
/** @var int position for regex in full placeholder lookup: numbered */
public const LOOOKUP_NUMBERED_POS = 4;
/** @var int matches position for replacement and single lookup */
public const MATCHING_POS = 2;
/** /**
* Convert PDO type query with placeholders to \PG style and vica versa * Convert PDO type query with placeholders to \PG style and vica versa
@@ -132,11 +133,12 @@ class ConvertPlaceholder
$found = -1; $found = -1;
} }
/** @var array<string> 1: named */ /** @var array<string> 1: named */
$named_matches = array_filter($matches[1]); $named_matches = array_filter($matches[self::LOOOKUP_NAMED_POS]);
/** @var array<string> 2: open ? */ /** @var array<string> 2: open ? */
$qmark_matches = array_filter($matches[2]); $qmark_matches = array_filter($matches[self::LOOOKUP_QUESTION_MARK_POS]);
/** @var array<string> 3: $n matches */ /** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]); $numbered_matches = array_filter($matches[self::LOOOKUP_NUMBERED_POS]);
// print "**MATCHES**: <pre>" . print_r($matches, true) . "</pre>";
// count matches // count matches
$count_named = count(array_unique($named_matches)); $count_named = count(array_unique($named_matches));
$count_qmark = count($qmark_matches); $count_qmark = count($qmark_matches);
@@ -235,38 +237,37 @@ class ConvertPlaceholder
$empty_params = $converted_placeholders['original']['empty_params']; $empty_params = $converted_placeholders['original']['empty_params'];
switch ($converted_placeholders['type']) { switch ($converted_placeholders['type']) {
case 'named': case 'named':
// 0: full // 1: replace part :named
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part :named
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_NAMED, self::REGEX_REPLACE_NAMED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table if (!isset($matches[self::MATCHING_POS])) {
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
209
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = '$' . $pos; $params_lookup[$match] = '$' . $pos;
// skip params setup if param list is empty // skip params setup if param list is empty
if (!$empty_params) { if (!$empty_params) {
$params_new[] = $params[$matches[3]] ?? $params_new[] = $params[$match] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params list', 'Cannot lookup ' . $match . ' in params list',
210 210
); );
} }
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return $params_lookup[$match] ??
empty($matches[3]) ? throw new \RuntimeException(
$matches[2] : 'Cannot lookup ' . $match . ' in params lookup list',
$params_lookup[$matches[3]] ?? 211
throw new \RuntimeException( );
'Cannot lookup ' . $matches[3] . ' in params lookup list',
211
)
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );
@@ -276,61 +277,61 @@ class ConvertPlaceholder
// order and data stays the same // order and data stays the same
$params_new = $params ?? []; $params_new = $params ?? [];
} }
// 0: full // 1: replace part ?
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part ?
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_QUESTION_MARK, self::REGEX_REPLACE_QUESTION_MARK,
function ($matches) use (&$pos, &$params_lookup) { function ($matches) use (&$pos, &$params_lookup) {
if (!isset($matches[self::MATCHING_POS])) {
throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
229
);
}
$match = $matches[self::MATCHING_POS];
// only count pos up for actual replacements we will do // only count pos up for actual replacements we will do
if (!empty($matches[3])) { if (!empty($match)) {
$pos++; $pos++;
$params_lookup[] = '$' . $pos; $params_lookup[] = '$' . $pos;
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return '$' . $pos;
empty($matches[3]) ?
$matches[2] :
'$' . $pos
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );
break; break;
case 'numbered': case 'numbered':
// 0: full // 1: replace part $numbered
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part $numbered
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_NUMBERED, self::REGEX_REPLACE_NUMBERED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table if (!isset($matches[self::MATCHING_POS])) {
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
239
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = ':' . $pos . '_named'; $params_lookup[$match] = ':' . $pos . '_named';
// skip params setup if param list is empty // skip params setup if param list is empty
if (!$empty_params) { if (!$empty_params) {
$params_new[] = $params[($pos - 1)] ?? $params_new[] = $params[($pos - 1)] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . ($pos - 1) . ' in params list', 'Cannot lookup ' . ($pos - 1) . ' in params list',
220 230
); );
} }
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return $params_lookup[$match] ??
empty($matches[3]) ? throw new \RuntimeException(
$matches[2] : 'Cannot lookup ' . $match . ' in params lookup list',
$params_lookup[$matches[3]] ?? 231
throw new \RuntimeException( );
'Cannot lookup ' . $matches[3] . ' in params lookup list',
221
)
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );