sql >> Base de Datos >  >> RDS >> Mysql

Pertenece a la relación de muchos en Laravel a través de múltiples bases de datos

Muy simple:

public function bs()
{
    $database = $this->getConnection()->getDatabaseName();
    return $this->belongsToMany('B', "$database.a_bs", 'a_id', 'b_id');
}

Obtengo el nombre de la base de datos de forma dinámica porque mi conexión está configurada en función de una variable de entorno. Laravel parece asumir que la tabla dinámica existe en la misma base de datos que la relación de destino, por lo que esto lo obligará a buscar en la base de datos correspondiente al modelo en el que se encuentra este método, su reino 'A'.

Si no le preocupan las bases de datos SQLite, es decir, en el ámbito de una prueba unitaria, eso es todo lo que necesita. Pero si es así, sigue leyendo.

En primer lugar, el ejemplo anterior no es suficiente por sí solo. El valor de $base de datos terminaría siendo una ruta de archivo, por lo que debe asignarle un alias a algo que no rompa una declaración SQL y que sea accesible para la conexión actual. "ATTACH DATABASE '$database' AS $name" es como se hace eso:

public function bs()
{
    $database = $this->getConnection()->getDatabaseName();
    if (is_file($database)) {
        $connection = app('B')->getConnection()->getName();
        $name = $this->getConnection()->getName();
        \Illuminate\Support\Facades\DB::connection($connection)->statement("ATTACH DATABASE '$database' AS $name");
        $database = $name;
    }
    return $this->belongsToMany('B', "$database.a_bs", 'a_id', 'b_id');
}

Advertencia:las transacciones estropean esto: Si la conexión actual utiliza transacciones, la instrucción ATTACH DATABASE fallará. Tu puedes usar transacciones en él después ejecutando esa declaración sin embargo.

Mientras que, si el relacionado conexión utiliza transacciones, los datos resultantes se volverán silenciosos e invisibles para el actual. Esto me volvió loco por más tiempo del que me gustaría admitir, porque mis consultas se ejecutaron sin errores, pero seguían apareciendo vacías. Parece que solo los datos verdaderamente escritos en la base de datos adjunta son realmente accesibles para la base de datos adjunta.

Entonces, después de verse obligado a escribir en su base de datos adjunta, es posible que aún desee que su prueba se limpie después de sí misma. Una solución simple sería usar $this->artisan('migrate:rollback', ['--database' => $attachedConnectionName]); . Pero si tiene varias pruebas que necesitan las mismas tablas, esto no es muy eficiente, ya que obliga a tener que reconstruirlas cada vez.

Una mejor opción sería truncar las tablas, pero dejando su estructura intacta:

//Get all tables within the attached database
collect(DB::connection($database)->select("SELECT name FROM sqlite_master WHERE type = 'table'"))->each(function ($table) use ($name) {
        //Clear all entries for the table
        DB::connection($database)->delete("DELETE FROM '$table->name'");
        //Reset any auto-incremented index value
        DB::connection($database)->delete("DELETE FROM sqlite_sequence WHERE name = '$table->name'");
    });
}

Esto borrará todos los datos de esa conexión , pero no hay ninguna razón por la que no pueda aplicar algún tipo de filtro a eso como mejor le parezca. Alternativamente, puede aprovechar el hecho de que las bases de datos SQLite son archivos de fácil acceso, y simplemente copie el archivo adjunto en un archivo temporal y utilícelo para sobrescribir la fuente después de que la prueba haya terminado de ejecutarse. El resultado sería funcionalmente idéntico a una transacción.