Skip to content
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

[11.x] Support named in-memory SQLite connections #53635

Open
wants to merge 2 commits into
base: 11.x
Choose a base branch
from

Conversation

stancl
Copy link
Contributor

@stancl stancl commented Nov 22, 2024

SQLite supports named in-memory databases. The benefit of named connections is that you can essentially "reconnect" to the same memory region even without passing a PDO instance around. Only the DSN needs to be the same.

Here's an example:

<?php

function ensure_table_exists(PDO $conn) {
    if (! $conn->query('SELECT name FROM sqlite_master WHERE type="table" AND name="my_table"')->fetch()) {
        $conn->exec('CREATE TABLE my_table (name text)');
    }
}

function foo(string $dsn) {
    $conn = new PDO($dsn);
    ensure_table_exists($conn);

    $conn->exec('INSERT INTO my_table VALUES ("foo")');
    var_dump(array_map(fn ($res) => $res[0], $conn->query('SELECT * from my_table')->fetchAll())); // foo

    return $conn;
}

function bar(string $dsn) {
    $conn = new PDO($dsn);
    ensure_table_exists($conn);

    $conn->exec('INSERT INTO my_table VALUES ("bar")');
    var_dump(array_map(fn ($res) => $res[0], $conn->query('SELECT * from my_table')->fetchAll())); // foo + bar

    return $conn;
}

$dsn = "sqlite:file:mydb?mode=memory&cache=shared";
// $dsn = 'sqlite::memory:';

$_ = foo($dsn);
bar($dsn);

It shows how you can connect to the same database, without passing the PDO instance directly, as long as the DSN string is identical. foo() will log foo while bar() will log foo and bar. The only condition here is that the connection is kept alive somehow.

If you swap the $dsn definitions, both foo() and bar() will create their own database when the PDO connection is created (and free it once $conn goes out of scope). Additionally, if you remove the $_ =, the memory of the database will be freed as there's no longer any open connection, so it'll behave identically to :memory:.

Practical use case

This lets me support :memory: in my tenancy package for testing tenant databases, providing a nice speedup. During the tenant creation process there's a lot of logic that essentially keeps connecting to and disconnecting from the tenant DB. Users may also switch to the central context using tenancy()->end() or tenancy()->central(closure) which closes the tenant connection.

With named databases, I only need to keep a connection to the tenant DB alive somewhere (literally just register_shutdown_function(fn () => $conn) works) and then the package can seamlessly connect to and disconnect from tenant databases that live entirely in process memory and don't need any special cleanup.

Code style

I could just check for mode=memory but that could in theory be part of a real filename. Don't think it'd make anything vulnerable if the realpath() check got skipped, but I went with ?mode || &mode just in case since it's a little more restrictive.

The DSN syntax for named in-memory databases is just mode=memory in URI format typically paired with cache=shared. The order being arbitrary.

I also tried to respect the 3 char rule in the comment but couldn't think of anything that'd fit here better, so the last line is an extra character shorter.

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.

1 participant