Skip to content

Feature mysql driver support #511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from

Conversation

jakob-info
Copy link
Contributor

@jakob-info jakob-info commented Apr 29, 2025

Laravels query builder supports mysql out of the box. Only raw sql must be altered for mysql support. Thankfully, apart from the inital migration, only four files contain some postgres specifc queries. Fixes #488.

Migrations

The initial migration is a big raw sql file. It got adapted to mysqls syntax whenever possible. When migrating the schema.sql or the schema.mysql.sql get executed based on the selected driver. Here are the modifications

  • Mysql does not support "create index if not exists". That does not matter. Migrations get executed only once.
  • Mysql does not append the type to a default value e. g. 'USD'::character varying is just 'USD'
  • Mysql calls a serial -> int, bigserial -> bigint, jsonb -> json, varchar without a length -> text, "generated always as identity" -> "auto_increment" and numeric ->decimal.
  • In foreign key constraint, mysql additionally needs the name of the primary key. "references events" -> "references events(id)"
  • Mysql does not support GIN indecies. A fulltext index was used as an replacement.
  • Index on arrays like [0, 1, 2, 3] as json are not possible only on keys like {key: "value"}
  • Normal indecies have a maximum length in mysql. Therefore a lenght has to be specified for text columns.
  • Renaming an index requies the table name in mysql
  • uuid() can't be the default value of a column in mysql. With a trigger it can be emulated.

boolean casting

Mysql does not have a native boolean type. Instead it uses tinying(1) which can only be 0 or 1. In c that would be considered a boolean but PHP differentiates and does not allow implicit int to boolean casting. The programm adds that to the function getCastMap(): array. It does not affect postgresql. It does not add complexity nor potential for bugs.

prepared statements and named bindings

In the official php docs (https://www.php.net/manual/en/pdo.prepare.php) it says:

You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.

The code fixes that in a separat commit. Apparently,it still worked anyway in postgresql.

single binding array syntax

In psql arrays can be passed as one value and than unnested. This means data is grouped by column not by row and thus only needing |columns| many placeholders/bindings. Mysql does not has an array datatype outside of json. I decided to not change that code but rather branch by database driver to leave code untoched and leave in potential performance gains.
The mysql specific code updates/inserts data row by row in the traditional way in a foreach loop. It would be possible to pack it into one query with many questionmarks as placeholders/bindings but i don't know how many rows will be updated. Mysql like postgresql has a prepared parameter count limit in the thousands so at some point it had to be split into multiple queries. To avoid that added complexity, it's a simple loop.

different time syntax

Mysql und psql use different syntax for time. The new code differentiates with a simple variable and if statement.

Checklist

  • I have read the contributing guidelines.
  • My code is of good quality and follows the coding standards of the project.
  • I have tested my changes, and they work as expected.

Thank you for your contribution! 🎉

@daveearley
Copy link
Contributor

Thanks so much for this contribution. I really appreciate the time and thought you’ve put into it.

That said, I’m going to hold off on merging this for now. There are a number of large features in the pipeline, and introducing support for two database engines would slow things down quite a bit. A few key reasons:

  • Every new feature would need to be tested against both MySQL and PostgreSQL
  • Migrations and queries become more complex and harder to maintain
  • Some Postgres-specific features like generate_series, jsonb, and partial indexes either don’t exist or require workarounds in MySQL. These will be used in the upcoming recurring events feature
  • Supporting both engines and introducing conditional logic increases the risk of bugs that are easy to introduce but hard to detect
  • Planned e2e tests in CI will become more complex

I'm not ruling this out permanently. If there's strong community demand and enough interest in maintaining MySQL compatibility, I'm open to revisiting it in the future.

Thanks again. This is genuinely appreciated.

@jakob-info
Copy link
Contributor Author

Hi @daveearley ,
Thanks for your answer. I didn't get a notification for your reply. I appreciate the acknowledgment, I see your argument that it's extra work that doesn't benefit you or users of the Docker version that directly comes with PostgreSQL.

I want to clarify that I mean it as unofficial support. In my opinion, unofficial means that the project doesn't make any guarantees that it's tested or works with MySQL, like Wikimedia doesn't for PostgreSQL. Apart from adding if clauses for MySQL, I tried not to touch the code and leave everything as it is for PostgreSQL.

I would find it pretty cool if all tests ran for MySQL and PostgreSQL, but I totally understand that there's a limit on how many GitHub Actions you get per month for free, and running tests twice would use that up twice as fast. That's not economical for you.

The main work was to get the initial migration to work, which is all written in plain PostgreSQL. Existing migrations don't change; therefore, adding separate ones for MySQL doesn't introduce extra complexity, unlike queries that could change over time.
On that note, I found a package that adds support for PostgreSQL features to Laravel migrations, like partial indexes. Please have a look at it regardless of my MySQL debate: https://github.com/umbrellio/laravel-pg-extensions

Schema::create('table', function (Blueprint $table) {
    $table->string('code'); 
    $table->softDeletes();
    $table->uniquePartial('code')->whereNull('deleted_at');
});

The type jsonb is fully compatible with json and partial indexes can just be indexes without extra work.

I agree, using conditional logic to run different raw SQL queries for MySQL and PostgreSQL maybe wasn't the right choice. It introduces the risk of bugs when the code for Postgres gets changed and MySQL gets forgotten, or it is double the work to maintain both versions. As you aren't interested in merging this, you probably don't want me to rewrite the queries without the conditional logic in Laravel's query builder?

My idea was that with early unofficial support for MySQL, the project could incorporate tiny additions that don't cost any more time or mean more work but help the project to be more open to MySQL. For example, you already cast some datetimes and arrays with Laravel's cast functionality. If the project would cast all datetime and boolean values, that would help a lot.

Is there any chance I can convince you to merge the changes for the old migrations and the casts, maybe in a different merge request?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🌟Inofficial mysql support
2 participants