diff --git a/docs/development-environment/setup.rst b/docs/development-environment/setup.rst index 44891d27..4b85ab10 100644 --- a/docs/development-environment/setup.rst +++ b/docs/development-environment/setup.rst @@ -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 'bak_', ); +**PostgreSQL configuration:** + +.. code-block:: php + + '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 16 or later is required for PostgreSQL support. Ensure the ``pdo_pgsql`` PHP extension is installed. + * Run the following command and add your own options: .. code-block:: bash diff --git a/docs/plugins/database.rst b/docs/plugins/database.rst index bcc43417..2d164e9d 100644 --- a/docs/plugins/database.rst +++ b/docs/plugins/database.rst @@ -363,4 +363,145 @@ 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 \ No newline at end of file + :returntype: string + +Database compatibility +********************** + +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 provides helper methods in ``CommonRepository`` to handle this. + +**Using the helper methods:** + +.. code-block:: php + + // In a repository extending CommonRepository + $qb = $this->getEntityManager()->getConnection()->createQueryBuilder(); + + // Case-insensitive LIKE - uses ILIKE on PostgreSQL, LIKE on MySQL/MariaDB + $qb->andWhere( + $this->getILikeExpression($qb, 'l.email', ':search') + ); + + // Or wrap the column with LOWER() for case-insensitive comparison + $qb->andWhere( + $this->getLowerLikeExpression($qb, 'l.firstname', ':search') + ); + +**Available methods in CommonRepository:** + +- ``getILikeExpression(QueryBuilder $qb, string $column, string $parameter)``: Returns platform-appropriate case-insensitive ``LIKE`` expression +- ``getLowerLikeExpression(QueryBuilder $qb, string $column, string $parameter)``: Wraps the column with ``LOWER()`` for case-insensitive comparison +- ``isPostgreSql()``: Returns ``TRUE`` if connected to a PostgreSQL database + +.. 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. + +**Use quoted identifiers for camelCase aliases:** + +.. 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 enabled - 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, detect the database type using the connection's platform: + +.. code-block:: php + + use Doctrine\DBAL\Platforms\PostgreSQLPlatform; + + $connection = $this->getEntityManager()->getConnection(); + $platform = $connection->getDatabasePlatform(); + + if ($platform instanceof PostgreSQLPlatform) { + // PostgreSQL-specific logic + } else { + // MySQL/MariaDB logic + } + +Repositories extending ``CommonRepository`` can use the ``isPostgreSql()`` helper method. + +Best practices +============== + +1. **Use Doctrine's ORM and QueryBuilder** - Doctrine abstracts most database differences. Avoid raw SQL when possible. + +2. **Test on multiple databases** - Mautic's CI tests against MySQL, MariaDB, and PostgreSQL. Run your Plugin tests against all platforms before release. + +3. **Quote mixed-case aliases** - When using custom column aliases with ``camelCase`` names in raw SQL, always quote them. + +4. **Use repository helper methods** - ``CommonRepository`` provides cross-platform helpers for common operations like case-insensitive searches. \ No newline at end of file diff --git a/docs/testing/e2e_test_suite.rst b/docs/testing/e2e_test_suite.rst index 46821ac1..8a19efa7 100644 --- a/docs/testing/e2e_test_suite.rst +++ b/docs/testing/e2e_test_suite.rst @@ -233,6 +233,64 @@ 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 +=========================== + +.. 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: + +1. 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 + +2. Ensure the ``pdo_pgsql`` PHP extension is installed and enabled. + +3. Run the test suite as normal: + +.. code-block:: bash + + bin/phpunit + +Database-specific test considerations +===================================== + +Keep these database differences in mind when writing tests: + +- **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. + +For detailed guidance on writing database-agnostic code, refer to the :doc:`/plugins/database` documentation. + Contributing ************