Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/styles/config/vocabularies/Mautic/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ noindex
OAuth
OAuth1a
OAuth2
ORM
Packagist
patch
PATCH
Expand Down
23 changes: 23 additions & 0 deletions docs/development-environment/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ You can also run the install process from command line:
* Add a ``local.php`` file in ``app/config``
* Edit the ``local.php`` file using the following template (Mautic adapts to new local settings):

**MySQL/MariaDB configuration:**

.. code-block:: php

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostgreSQL configuration example from PR #15926. Uses pdo_pgsql driver on port 5432, requires PostgreSQL 16+.

Source: mautic/mautic#15926

<?php
Expand All @@ -35,6 +37,27 @@ You can also run the install process from command line:
'db_backup_prefix' => 'bak_',
);

**PostgreSQL configuration:**

.. code-block:: php

<?php
$parameters = array(
'db_driver' => 'pdo_pgsql',
'db_host' => 'localhost',
'db_table_prefix' => null,
'db_port' => '5432',
'db_name' => 'mautic',
'db_user' => 'postgres',
'db_password' => 'postgres_password',
'db_backup_tables' => true,
'db_backup_prefix' => 'bak_',
);

.. note::

PostgreSQL support requires PostgreSQL 16 or later. Install the ``pdo_pgsql`` PHP extension before proceeding.

* Run the following command and add your own options:

.. code-block:: bash
Expand Down
157 changes: 156 additions & 1 deletion docs/plugins/database.rst
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,159 @@ Define migrations in the Plugin's ``Migrations`` directory. The file and class n
:param array $columns: Array of columns to included in the index.

:return: ``INDEX {tableName} ($columns...)`` statement
:returntype: string
:returntype: string

Database compatibility

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Database compatibility section based on PR #15926. Key implementation files: CommonRepository.php, GeneratedColumnProvider.php.

Source: mautic/mautic#15926

**********************

Mautic 7.x supports MySQL, MariaDB, and PostgreSQL. Follow these patterns to ensure your queries work across all platforms.

Supported databases
===================

.. list-table::
:widths: 30 30 40
:header-rows: 1

* - Database
- Minimum version
- PHP extension
* - MySQL
- 8.0
- ``pdo_mysql``
* - MariaDB
- 10.6
- ``pdo_mysql``
* - PostgreSQL
- 16
- ``pdo_pgsql``

.. vale off

Case-insensitive string matching
================================

.. vale on

MySQL and MariaDB use case-insensitive ``LIKE`` comparisons by default. PostgreSQL's ``LIKE`` is case-sensitive. Mautic centralizes all platform-specific helpers in ``Mautic\CoreBundle\Doctrine\DatabasePlatform``.

**Using the helper methods:**

.. code-block:: php

use Mautic\CoreBundle\Doctrine\DatabasePlatform;

$connection = $this->getEntityManager()->getConnection();
$platform = $connection->getDatabasePlatform();
$qb = $connection->createQueryBuilder();

// Case-insensitive LIKE - uses ILIKE on PostgreSQL, LIKE on MySQL/MariaDB
$qb->andWhere(
DatabasePlatform::getCaseInsensitiveLike($platform, 'l.email', ':search')
);

// Apply LOWER() to the column for case-insensitive comparison
$qb->andWhere(
DatabasePlatform::getCaseInsensitiveLike(
$platform,
'l.firstname',
':search',
DatabasePlatform::FLAG_LOWER_COLUMN
)
);

.. vale off

**Available methods in DatabasePlatform:**

.. vale on

* ``getCaseInsensitiveLike($platform, $column, $valueOrParameter, $flags)``: returns platform-appropriate case-insensitive ``LIKE`` expression. Supports flags for ``LOWER()`` handling and negation.
* ``getRegexpExpression($platform, $column, $pattern, $negative)``: handles ``REGEXP`` differences between MySQL and PostgreSQL.
* ``isPostgreSQL($platform)``: returns ``TRUE`` if the platform is PostgreSQL.
* ``isMySQL($platform)``: returns ``TRUE`` if the platform is MySQL or MariaDB.

.. vale off

Column and alias quoting
========================

.. vale on

PostgreSQL lowercases unquoted identifiers automatically. This causes issues with Mautic's ``camelCase`` column aliases. Always quote identifiers in raw SQL queries that use mixed-case names.

.. vale off

**Use quoted identifiers for camelCase aliases:**
Comment thread
adiati98 marked this conversation as resolved.

.. vale on

.. code-block:: php

// Correct - aliases are quoted
$qb->select('l.id, l.first_name AS "firstName", l.date_added AS "dateAdded"');

// Incorrect - PostgreSQL converts to lowercase
$qb->select('l.id, l.first_name AS firstName');

Doctrine's QueryBuilder handles quoting automatically when you use proper field mappings. You only need manual quoting for raw SQL or custom column aliases.

.. vale off

GROUP BY requirements
=====================

.. vale on

PostgreSQL enforces strict ``GROUP BY`` rules, and MySQL 8+ does the same when ``ONLY_FULL_GROUP_BY`` SQL mode is active - the default in strict mode. Every column in the ``SELECT`` clause must appear in the ``GROUP BY`` clause or use an aggregate function.

Writing compliant ``GROUP BY`` clauses ensures compatibility across all supported databases and SQL modes.

Mautic's Report Builder corrects ``GROUP BY`` clauses automatically. If you're building custom Reports or queries with aggregates, include all non-aggregated columns in the ``GROUP BY`` clause.

**Correct pattern:**

.. code-block:: php

$qb->select('l.id, l.email, COUNT(e.id) as emailCount')
->from('leads', 'l')
->leftJoin('l', 'emails', 'e', 'e.lead_id = l.id')
->groupBy('l.id, l.email'); // All non-aggregate columns included

**Incorrect pattern:**

.. code-block:: php

// This fails on PostgreSQL - l.email not in GROUP BY
$qb->select('l.id, l.email, COUNT(e.id) as emailCount')
->from('leads', 'l')
->leftJoin('l', 'emails', 'e', 'e.lead_id = l.id')
->groupBy('l.id');

Detecting the database platform
===============================

When you need platform-specific query logic, use the static helpers in ``DatabasePlatform``:

.. code-block:: php

use Mautic\CoreBundle\Doctrine\DatabasePlatform;

$connection = $this->getEntityManager()->getConnection();
$platform = $connection->getDatabasePlatform();

if (DatabasePlatform::isPostgreSQL($platform)) {
// PostgreSQL-specific logic
} else {
// MySQL/MariaDB logic
}

``DatabasePlatform`` is the single source of truth for platform differences. Use its helpers instead of writing platform checks manually to avoid drift as the codebase evolves.

Best practices
==============

#. **Use Doctrine's Object Relational Mapper - ORM - and QueryBuilder** - Doctrine abstracts most database differences. Avoid raw SQL when possible.
#. **Test on multiple databases** - Mautic's CI tests against MySQL, MariaDB, and PostgreSQL. Run your Plugin tests against all platforms before release.
#. **Quote mixed-case aliases** - When using custom column aliases with ``camelCase`` names in raw SQL, always quote them.
#. **Use DatabasePlatform helpers** - ``Mautic\CoreBundle\Doctrine\DatabasePlatform`` provides cross-platform helpers for case-insensitive searches, regular expressions, date handling, and platform detection. Consult its source for the full list of available methods.
64 changes: 64 additions & 0 deletions docs/testing/e2e_test_suite.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,70 @@ You can watch your tests run in an automated browser by visiting the following U

``Password: secret``

Testing on multiple databases
*****************************

Mautic supports MySQL, MariaDB, and PostgreSQL. The CI pipeline runs tests against all supported databases. Make sure your code works across all databases when developing features or writing tests.

Supported database versions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-database testing section based on PR #15926. CI tests run on PostgreSQL 16/18, MariaDB 10.11/11.4, MySQL 8.4/9.4.

Source: mautic/mautic#15926

===========================

.. list-table::
:header-rows: 1

* - Database
- Tested versions
* - PostgreSQL
- 16, 18
* - MariaDB
- 10.11, 11.4
* - MySQL
- 8.4, 9.4

Configuring your test environment for PostgreSQL
================================================

To run tests locally against PostgreSQL:

#. Update your ``.env.test.local`` with PostgreSQL credentials:

.. code-block:: bash

# .env.test.local
DB_DRIVER=pdo_pgsql
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWD=your_password
DB_NAME=mautic_test

#. Install and enable the ``pdo_pgsql`` PHP extension.

#. Run the test suite as normal:

.. code-block:: bash

bin/phpunit

Database-specific test considerations
=====================================

Keep these database differences in mind when writing tests:

.. vale off

* **Case sensitivity**: PostgreSQL ``LIKE`` is case-sensitive; MySQL/MariaDB ``LIKE`` isn't. Use Mautic's helper methods for case-insensitive matching.
- **GROUP BY strictness**: PostgreSQL and MySQL 8+ strict mode require all non-aggregated ``SELECT`` columns in ``GROUP BY``.
- **Identifier quoting**: PostgreSQL lowercases unquoted identifiers. Quote ``camelCase`` aliases in raw SQL.

.. vale on

.. vale off

For detailed guidance on writing database-agnostic code, refer to the :doc:`/plugins/database` documentation.
Comment thread
adiati98 marked this conversation as resolved.

.. vale on

Contributing
************

Expand Down
Loading