feat(db): add MySQL 8.4 support (#4048)

Add MySQL 8.4 compatibility, including a utf8mb4 conversion migration,
hardened foreign key handling, and expanded CI database coverage.

Migrations:
- Add Mysql84Utf8mb4Conversion migration using
  sqlscripts/3.4.3_mysql84_utf8mb4_conversion.sql via executeScript()
- Drop ospos_people composite index before conversion, since its
  prefix length exceeds the 3072-byte key limit under utf8mb4
- Add assertNoCollationCollisions() pre-flight check to fail fast on
  unique values that collide under utf8mb4_unicode_520_ci, instead of
  hitting "Duplicate entry" partway through ALTER TABLE
- Wrap the conversion in try/catch so foreign keys are recreated on
  failure; log restore errors separately so they don't mask the
  original exception
- Throw RuntimeException if re-adding the people index fails
- Remove the re-add of the sales_items_taxes FK in down(), which
  MySQL 8.4 rejects without a unique key
- Revert edits to historical migrations; fix FK handling and table
  names

migration_helper:
- Add shared dropAllForeignKeyConstraints() and
  recreateForeignKeyConstraints(); table/column now optional, dropping
  all FKs in the schema when omitted
- Match CONSTRAINT_SCHEMA when joining KEY_COLUMN_USAGE to
  REFERENTIAL_CONSTRAINTS to avoid cross-schema name collisions
- Rebuild FK lookup with the query builder and bound values instead
  of interpolated SQL

CI/tests:
- Add mysql:5.7, mariadb:10.6 and mariadb:10.11 to PHPUnit matrix
- Use master instead of latest image tag; improve CI robustness
- Mock table() in FailedQueryConnectionTrait so query builder chains
  work against the failing connection

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: aliworksx08 <57456290+aliworksx08@users.noreply.github.com>
Co-authored-by: jekkos <jeroen.peelaerts@gmail.com>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 14:24:51 +04:00
1 parent 312a8776ec
commit e20fb2a792
12 files changed
+269 -57

No files matched your search

+1
View File
@@ -28,6 +28,7 @@ app.allowedHostnames=''
#--------------------------------------------------------------------
# DATABASE
#--------------------------------------------------------------------
# MySQL 5.7, 8.0, 8.4 (LTS) and MariaDB 10.x supported. MySQL 8.4 requires mysqlnd (default in PHP 8.1+).
database.default.hostname='localhost'
database.default.database='ospos'
+25 -8
View File
@@ -27,7 +27,7 @@ permissions:
jobs:
test:
name: PHP ${{ matrix.php-version }} Tests
name: PHP ${{ matrix.php-version }} / ${{ matrix.db-image }} Tests
runs-on: ubuntu-22.04
strategy:
@@ -37,6 +37,13 @@ jobs:
- '8.2'
- '8.3'
- '8.4'
db-image:
- 'mariadb:10.5'
- 'mariadb:10.6'
- 'mariadb:10.11'
- 'mysql:5.7'
- 'mysql:8.0'
- 'mysql:8.4'
steps:
- name: Checkout
@@ -68,7 +75,7 @@ jobs:
- name: Install npm dependencies
run: npm install
- name: Start MariaDB
- name: Start database
run: |
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=root \
@@ -76,13 +83,19 @@ jobs:
-e MYSQL_USER=admin \
-e MYSQL_PASSWORD=pointofsale \
-p 3306:3306 \
mariadb:10.5
# Wait for MariaDB to be ready
${{ matrix.db-image }}
# Wait for database to be ready (max 60 attempts)
retries=0
until docker exec mysql mysqladmin ping -h 127.0.0.1 -u root -proot --silent; do
echo "Waiting for MariaDB..."
retries=$((retries + 1))
if [ $retries -ge 60 ]; then
echo "Database failed to start after 60 attempts"
exit 1
fi
echo "Waiting for database... (attempt $retries/60)"
sleep 2
done
echo "MariaDB is ready!"
echo "Database is ready!"
# Grant admin the CREATE/DROP privileges it needs to drop and
# recreate the test database at the start of each test class.
@@ -140,14 +153,18 @@ jobs:
MYSQL_HOST_NAME: 127.0.0.1
run: composer test -- --no-coverage --log-junit test-results/junit.xml
- name: Set artifact name
if: always()
run: echo "DB_LABEL=$(echo '${{ matrix.db-image }}' | tr ':' '-')" >> $GITHUB_ENV
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-php-${{ matrix.php-version }}
name: test-results-php-${{ matrix.php-version }}-${{ env.DB_LABEL }}
path: test-results/
retention-days: 30
- name: Stop MariaDB
- name: Stop database
if: always()
run: docker stop mysql && docker rm mysql
@@ -59,8 +59,5 @@ class Migration_fix_keys_for_db_upgrade extends Migration
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes') . ' DROP FOREIGN KEY ospos_sales_items_taxes_ibfk_1');
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes')
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id) '
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id)');
}
}
@@ -0,0 +1,109 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use RuntimeException;
class Migration_Mysql84Utf8mb4Conversion extends Migration
{
public function up(): void
{
helper('migration');
$this->assertNoCollationCollisions();
$foreignKeys = dropAllForeignKeyConstraints();
try {
if (indexExists('people', 'first_name')) {
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('people') . ' DROP INDEX first_name');
}
$script = APPPATH . 'Database/Migrations/sqlscripts/3.4.3_mysql84_utf8mb4_conversion.sql';
if (!executeScript($script)) {
throw new RuntimeException('Failed to execute utf8mb4 conversion migration: ' . $script);
}
if (!$this->db->query('ALTER TABLE ' . $this->db->prefixTable('people')
. ' ADD INDEX(`first_name`(191), `last_name`(191), `email`(191), `phone_number`(191))')) {
throw new RuntimeException('Failed to add composite index on people table');
}
} catch (\Throwable $exception) {
log_message('error', 'utf8mb4 conversion failed; recovering dropped foreign keys: '
. json_encode($foreignKeys));
try {
recreateForeignKeyConstraints($foreignKeys);
} catch (\Throwable $restoreError) {
log_message('error', 'Foreign key recovery failed: ' . $restoreError->getMessage());
}
throw $exception;
}
recreateForeignKeyConstraints($foreignKeys);
}
// Intentionally irreversible: converting back to utf8 could cause data loss for utf8mb4 characters.
public function down(): void
{
}
/**
* utf8mb4_unicode_520_ci treats invisible/ignorable Unicode characters (e.g. the LTR
* mark U+200E) as equal weight to nothing, unlike utf8_general_ci. Values that were
* previously distinct under a UNIQUE constraint can collide once converted, which aborts
* the ALTER TABLE mid-script. Detect those collisions up front and fail with an actionable
* message rather than the raw "Duplicate entry" error partway through the conversion.
*/
private function assertNoCollationCollisions(): void
{
$uniqueColumns = $this->db->query("
SELECT DISTINCT s.TABLE_NAME, s.COLUMN_NAME
FROM information_schema.STATISTICS s
JOIN information_schema.COLUMNS c
ON c.TABLE_SCHEMA = s.TABLE_SCHEMA
AND c.TABLE_NAME = s.TABLE_NAME
AND c.COLUMN_NAME = s.COLUMN_NAME
WHERE s.TABLE_SCHEMA = DATABASE()
AND s.NON_UNIQUE = 0
AND s.TABLE_NAME LIKE '" . $this->db->getPrefix() . "%'
AND c.DATA_TYPE IN ('char', 'varchar', 'text', 'tinytext', 'mediumtext', 'longtext')
AND (
SELECT COUNT(*) FROM information_schema.STATISTICS s2
WHERE s2.TABLE_SCHEMA = s.TABLE_SCHEMA
AND s2.TABLE_NAME = s.TABLE_NAME
AND s2.INDEX_NAME = s.INDEX_NAME
) = 1
")->getResultArray();
$problems = [];
foreach ($uniqueColumns as $uniqueColumn) {
$table = $uniqueColumn['TABLE_NAME'];
$column = $uniqueColumn['COLUMN_NAME'];
$collisions = $this->db->query("
SELECT CONVERT(`$column` USING utf8mb4) COLLATE utf8mb4_unicode_520_ci AS value,
COUNT(*) AS count
FROM `$table`
WHERE `$column` IS NOT NULL
GROUP BY CONVERT(`$column` USING utf8mb4) COLLATE utf8mb4_unicode_520_ci
HAVING count > 1
")->getResultArray();
if ($collisions) {
$problems[] = "$table.$column (" . count($collisions) . ' colliding value(s))';
}
}
if ($problems) {
throw new RuntimeException(
'Cannot convert to utf8mb4: the following unique column(s) contain values that '
. 'collide under utf8mb4_unicode_520_ci (often due to invisible Unicode characters '
. "that this collation treats as equivalent):\n - " . implode("\n - ", $problems)
. "\nClean up or merge the colliding rows before retrying this migration."
);
}
}
}
@@ -0,0 +1,41 @@
-- Convert all tables to utf8mb4 for MySQL 8.4 compatibility
-- Foreign keys are dropped and recreated in the PHP migration to avoid collation mismatches
ALTER TABLE ospos_app_config CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_attribute_definitions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_attribute_links CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_attribute_values CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_cash_up CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_customers CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_customers_packages CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_customers_points CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_dinner_tables CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_employees CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_expenses CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_expense_categories CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_giftcards CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_grants CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_inventory CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_items CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_items_taxes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_item_kits CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_item_kit_items CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_item_quantities CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_migrations CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_modules CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_people CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_permissions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_receivings CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_receivings_items CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sales CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sales_items CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sales_items_taxes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sales_payments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sales_reward_points CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sales_taxes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_sessions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_stock_locations CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_suppliers CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_tax_categories CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_tax_codes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_tax_jurisdictions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE ospos_tax_rates CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
+79 -35
View File
@@ -118,40 +118,64 @@ function createPrimaryKey(string $table, string $index): void {
}
/**
* Drops all foreign key constraints that reference the provided table and column.
* Drops foreign key constraints that reference the provided table and column.
* When $table and $column are omitted, drops all foreign key constraints in the schema.
*
* A failed metadata query is treated as an error (thrown) rather than mistaken
* for "no constraints found", so callers never proceed with schema changes on a
* wrong assumption.
*
* @param string $table
* @param string $column
* @param string|null $table
* @param string|null $column
* @param BaseConnection|null $db Connection to use (defaults to the default connection).
*
* @return array containing the deleted constraints in case they need to be recreated after.
*
* @throws DatabaseException If the metadata query fails.
*/
function dropAllForeignKeyConstraints(string $table, string $column, ?BaseConnection $db = null): array {
function dropAllForeignKeyConstraints(?string $table = null, ?string $column = null, ?BaseConnection $db = null): array {
$db ??= Database::connect();
$result = $db->query("
SELECT DISTINCT
kcu.CONSTRAINT_NAME,
kcu.TABLE_NAME,
kcu.COLUMN_NAME,
kcu.REFERENCED_TABLE_NAME,
kcu.REFERENCED_COLUMN_NAME,
rc.DELETE_RULE,
rc.UPDATE_RULE
FROM information_schema.KEY_COLUMN_USAGE kcu
LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
AND kcu.TABLE_NAME = rc.TABLE_NAME
WHERE kcu.TABLE_SCHEMA = DATABASE()
AND ((kcu.REFERENCED_TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.REFERENCED_COLUMN_NAME = '$column')
OR (kcu.TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.COLUMN_NAME = '$column'))
AND rc.CONSTRAINT_NAME IS NOT NULL
");
$prefixedTable = $table !== null ? $db->getPrefix() . $table : null;
$prefix = overridePrefix();
$builder = $db->table('information_schema.KEY_COLUMN_USAGE kcu');
$builder->distinct();
$builder->select('kcu.CONSTRAINT_NAME, kcu.TABLE_NAME, kcu.COLUMN_NAME, kcu.REFERENCED_TABLE_NAME, kcu.REFERENCED_COLUMN_NAME, kcu.ORDINAL_POSITION, rc.DELETE_RULE, rc.UPDATE_RULE');
$builder->join(
'information_schema.REFERENTIAL_CONSTRAINTS rc',
'kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND kcu.TABLE_NAME = rc.TABLE_NAME',
'left'
);
if ($table !== null && $column !== null) {
$scopedBuilder = $db->table('information_schema.KEY_COLUMN_USAGE scoped');
$scopedBuilder->distinct();
$scopedBuilder->select('scoped.CONSTRAINT_NAME, scoped.TABLE_NAME');
$scopedBuilder->where('scoped.TABLE_SCHEMA', $db->database);
$scopedBuilder->groupStart();
$scopedBuilder->where('scoped.REFERENCED_TABLE_NAME', $prefixedTable);
$scopedBuilder->where('scoped.REFERENCED_COLUMN_NAME', $column);
$scopedBuilder->groupEnd();
$scopedBuilder->orGroupStart();
$scopedBuilder->where('scoped.TABLE_NAME', $prefixedTable);
$scopedBuilder->where('scoped.COLUMN_NAME', $column);
$scopedBuilder->groupEnd();
$builder->join(
'(' . $scopedBuilder->getCompiledSelect() . ') matched',
'matched.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND matched.TABLE_NAME = kcu.TABLE_NAME',
'inner',
false
);
}
$builder->where('kcu.TABLE_SCHEMA', $db->database);
$builder->where('rc.CONSTRAINT_NAME IS NOT NULL', null, false);
$builder->orderBy('kcu.CONSTRAINT_NAME');
$builder->orderBy('kcu.ORDINAL_POSITION');
$result = $builder->get();
overridePrefix($prefix);
if ($result === false) {
$detail = 'unknown error';
@@ -164,26 +188,46 @@ function dropAllForeignKeyConstraints(string $table, string $column, ?BaseConnec
// A disconnected or mocked connection may not support error(); keep the default.
}
throw new DatabaseException(sprintf(
'Failed to look up foreign key constraints referencing table "%s" column "%s": %s',
$table, $column, $detail
));
$scope = $table !== null && $column !== null
? sprintf('referencing table "%s" column "%s"', $table, $column)
: 'in the schema';
throw new DatabaseException(sprintf('Failed to look up foreign key constraints %s: %s', $scope, $detail));
}
$deletedConstraints = [];
foreach ($result->getResultArray() as $constraint) {
$deletedConstraints[] = [
'constraintName' => $constraint['CONSTRAINT_NAME'],
'tableName' => str_replace($db->DBPrefix, '', $constraint['TABLE_NAME']),
'columnName' => $constraint['COLUMN_NAME'],
'referencedTable' => str_replace($db->DBPrefix, '', $constraint['REFERENCED_TABLE_NAME']),
'referencedColumn' => $constraint['REFERENCED_COLUMN_NAME'],
'onDelete' => $constraint['DELETE_RULE'],
'onUpdate' => $constraint['UPDATE_RULE'],
];
$key = $constraint['TABLE_NAME'] . '.' . $constraint['CONSTRAINT_NAME'];
if (!isset($deletedConstraints[$key])) {
$deletedConstraints[$key] = [
'constraintName' => $constraint['CONSTRAINT_NAME'],
'tableName' => str_replace($db->DBPrefix, '', $constraint['TABLE_NAME']),
'columnName' => [],
'referencedTable' => str_replace($db->DBPrefix, '', $constraint['REFERENCED_TABLE_NAME']),
'referencedColumn' => [],
'onDelete' => $constraint['DELETE_RULE'],
'onUpdate' => $constraint['UPDATE_RULE'],
'seenPositions' => [],
];
}
$position = $constraint['ORDINAL_POSITION'];
if (!isset($deletedConstraints[$key]['seenPositions'][$position])) {
$deletedConstraints[$key]['seenPositions'][$position] = true;
$deletedConstraints[$key]['columnName'][] = $constraint['COLUMN_NAME'];
$deletedConstraints[$key]['referencedColumn'][] = $constraint['REFERENCED_COLUMN_NAME'];
}
}
foreach ($deletedConstraints as &$constraint) {
unset($constraint['seenPositions']);
}
unset($constraint);
$deletedConstraints = array_values($deletedConstraints);
if ($deletedConstraints) {
$forge = Database::forge();
foreach ($deletedConstraints as $foreignKey) {
+3 -3
View File
@@ -150,7 +150,7 @@ class Summary_payments extends Summary_report
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_taxes') . ' AS sales_taxes
ON sales.sale_id = sales_taxes.sale_id
WHERE ' . $where . ' AND sales_taxes.tax_type = \'1\'
GROUP BY sale_id
GROUP BY sales.sale_id
)'
);
@@ -164,7 +164,7 @@ class Summary_payments extends Summary_report
. 'ON sales.sale_id = sales_items.sale_id '
. 'LEFT OUTER JOIN ' . $this->db->prefixTable('sumpay_taxes_temp') . ' AS sumpay_taxes '
. 'ON sales.sale_id = sumpay_taxes.sale_id '
. 'WHERE ' . $where . ' GROUP BY sale_id
. 'WHERE ' . $where . ' GROUP BY sales.sale_id
)'
);
@@ -184,7 +184,7 @@ class Summary_payments extends Summary_report
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_payments') . ' AS sales_payments
ON sales.sale_id = sales_payments.sale_id
WHERE ' . $where . '
GROUP BY sale_id
GROUP BY sales.sale_id
)'
);
}
+2 -2
View File
@@ -58,7 +58,7 @@ abstract class Summary_report extends Report
INNER JOIN ' . $this->db->prefixTable('sales_items') . ' AS sales_items
ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.line = sales_items_taxes.line
WHERE ' . $where . '
GROUP BY sale_id, item_id, line
GROUP BY sales_items_taxes.sale_id, sales_items_taxes.item_id, sales_items_taxes.line
)'
);
@@ -76,7 +76,7 @@ abstract class Summary_report extends Report
INNER JOIN ' . $this->db->prefixTable('sales') . ' AS sales
ON sales.sale_id = payments.sale_id
WHERE ' . $where . '
GROUP BY sale_id
GROUP BY payments.sale_id
)'
);
+2 -2
View File
@@ -1088,7 +1088,7 @@ class Sale extends Model
INNER JOIN ' . $this->db->prefixTable('sales_items') . ' AS sales_items
ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.line = sales_items_taxes.line
WHERE ' . $where . '
GROUP BY sale_id, item_id, line
GROUP BY sales_items_taxes.sale_id, sales_items_taxes.item_id, sales_items_taxes.line
)';
$this->db->query($sql);
@@ -1174,7 +1174,7 @@ class Sale extends Model
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_items_taxes_temp') . ' AS sales_items_taxes
ON sales_items.sale_id = sales_items_taxes.sale_id AND sales_items.item_id = sales_items_taxes.item_id AND sales_items.line = sales_items_taxes.line
WHERE ' . $where . '
GROUP BY sale_id, item_id, line
GROUP BY sales.sale_id, items.item_id, sales_items.line, sales_items.discount_type
)';
$this->db->query($sql);
+1 -1
View File
@@ -12,7 +12,7 @@ networks:
services:
mysql:
image: mariadb:10.5
image: ${OSPOS_DB_IMAGE:-mariadb:10.5}
container_name: mysql
restart: always
expose:
+1 -1
View File
@@ -12,7 +12,7 @@ networks:
services:
mysql:
image: mariadb:10.5
image: ${OSPOS_DB_IMAGE:-mariadb:10.5}
container_name: mysql
restart: always
expose:
+5 -2
View File
@@ -9,14 +9,17 @@ trait FailedQueryConnectionTrait
{
protected function failingConnection(): BaseConnection
{
$driverClass = get_class(\Config\Database::connect($this->DBGroup));
$realConnection = \Config\Database::connect($this->DBGroup);
$driverClass = get_class($realConnection);
$builderClass = str_replace('Connection', 'Builder', $driverClass);
$db = $this->getMockBuilder($driverClass)
->disableOriginalConstructor()
->onlyMethods(['query', 'error'])
->onlyMethods(['query', 'error', 'table'])
->getMock();
$db->method('query')->willReturn(false);
$db->method('error')->willReturn(['code' => 1, 'message' => 'simulated query failure']);
$db->method('table')->willReturnCallback(static fn (string $tableName) => new $builderClass($tableName, $db));
return $db;
}