Skip to content

Commit

Permalink
Support materialized views
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed Feb 5, 2024
1 parent 5f84eb5 commit db9fc1a
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 9 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Use this command if you are in PowerShell on Windows (e.g. in VS Code):
- [Dropping Views](#dropping-views)
- [Checking For View Existence](#checking-for-view-existence)
- [Listing View Columns](#listing-view-columns)
- [Materialized Views](#materialized-views)

### Creating Views

Expand Down Expand Up @@ -104,6 +105,24 @@ use Staudenmeir\LaravelMigrationViews\Facades\Schema;
$columns = Schema::getViewColumnListing('active_users');
```

### Materialized Views

On PostgreSQL, you can create a materialized view with `createMaterializedView()`:

```php
use Staudenmeir\LaravelMigrationViews\Facades\Schema;

$query = DB::table('users')->where('active', true);

Schema::createMaterializedView('active_users', $query);
```

Use `refreshMaterializedView()` to refresh a materialized view:

```php
Schema::refreshMaterializedView('active_users');
```

## Contributing

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CODE OF CONDUCT](.github/CODE_OF_CONDUCT.md) for details.
2 changes: 2 additions & 0 deletions src/Facades/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
/**
* @method static void createView(string $name, $query, array $columns = null, bool $orReplace = false)
* @method static void createOrReplaceView(string $name, $query, array $columns = null)
* @method static void createMaterializedView(string $name, $query, array $columns = null)
* @method static void renameView(string $from, string $to)
* @method static void dropView(string $name, bool $ifExists = false)
* @method static void dropViewIfExists(string $name)
* @method static bool hasView(string $name)
* @method static array getViewColumnListing(string $name)
* @method static void refreshMaterializedView(string $name)
*/
class Schema extends Facade
{
Expand Down
33 changes: 31 additions & 2 deletions src/Schema/Builders/ManagesViews.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Staudenmeir\LaravelMigrationViews\Schema\Builders;

use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Str;

trait ManagesViews
Expand All @@ -13,14 +15,15 @@ trait ManagesViews
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|string $query
* @param array|null $columns
* @param bool $orReplace
* @param bool $materialized
* @return void
*/
public function createView($name, $query, array $columns = null, $orReplace = false)
public function createView($name, $query, array $columns = null, $orReplace = false, bool $materialized = false)
{
$query = $this->getQueryString($query);

$this->connection->statement(
$this->grammar->compileCreateView($name, $query, $columns, $orReplace)
$this->grammar->compileCreateView($name, $query, $columns, $orReplace, $materialized)
);
}

Expand All @@ -37,6 +40,19 @@ public function createOrReplaceView($name, $query, array $columns = null)
$this->createView($name, $query, $columns, true);
}

/**
* Create a new materialized view on the schema.
*
* @param string $name
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|string $query
* @param array|null $columns
* @return void
*/
public function createMaterializedView(string $name, EloquentBuilder|QueryBuilder $query, array $columns = null): void
{
$this->createView($name, $query, $columns, materialized: true);
}

/**
* Convert the query and its bindings to an SQL string.
*
Expand Down Expand Up @@ -155,4 +171,17 @@ public function getViewColumnListing($name)
{
return $this->getColumnListing($name);
}

/**
* Refresh a materialized view on the schema.
*
* @param string $name
* @return void
*/
public function refreshMaterializedView(string $name): void
{
$this->connection->statement(
$this->grammar->compileRefreshMaterializedView($name)
);
}
}
3 changes: 2 additions & 1 deletion src/Schema/Builders/SQLiteBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ class SQLiteBuilder extends Base
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|string $query
* @param array|null $columns
* @param bool $orReplace
* @param bool $materialized
* @return void
*/
public function createView($name, $query, array $columns = null, $orReplace = false)
public function createView($name, $query, array $columns = null, $orReplace = false, bool $materialized = false)
{
if ($orReplace) {
$this->dropViewIfExists($name);
Expand Down
3 changes: 2 additions & 1 deletion src/Schema/Builders/SqlServerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ class SqlServerBuilder extends Base
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|string $query
* @param array|null $columns
* @param bool $orReplace
* @param bool $materialized
* @return void
*/
public function createView($name, $query, array $columns = null, $orReplace = false)
public function createView($name, $query, array $columns = null, $orReplace = false, bool $materialized = false)
{
if ($orReplace) {
$this->dropViewIfExists($name);
Expand Down
20 changes: 17 additions & 3 deletions src/Schema/Grammars/CompilesViews.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ trait CompilesViews
* @param string $query
* @param array|null $columns
* @param bool $orReplace
* @param bool $materialized
* @return string
*/
public function compileCreateView($name, $query, $columns, $orReplace)
public function compileCreateView($name, $query, $columns, $orReplace, bool $materialized = false)
{
$orReplace = $orReplace ? 'or replace ' : '';
$orReplaceSql = $orReplace ? 'or replace ' : '';

$materializedSql = $materialized ? 'materialized ' : '';

$columns = $columns ? '('.$this->columnize($columns).') ' : '';

return 'create '.$orReplace.'view '.$this->wrapTable($name).' '.$columns.'as '.$query;
return 'create '.$orReplaceSql.$materializedSql.'view '.$this->wrapTable($name).' '.$columns.'as '.$query;
}

/**
Expand All @@ -45,4 +48,15 @@ public function compileViewExists()
{
return 'select * from information_schema.views where table_schema = ? and table_name = ?';
}

/**
* Compile the query to refresh a materialized view.
*
* @param string $name
* @return string
*/
public function compileRefreshMaterializedView(string $name): string
{
return 'refresh materialized view ' . $this->wrapTable($name);
}
}
38 changes: 37 additions & 1 deletion tests/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function testCreateViewWithBooleanBinding()

public function testCreateViewWithObjectBinding()
{
$object = new class() {
$object = new class () {
public function __toString()
{
return "O'Brien";
Expand Down Expand Up @@ -66,6 +66,24 @@ public function testCreateOrReplaceView()
$this->assertCount(1, $users);
}

public function testCreateMaterializedView()
{
if ($this->connection !== 'pgsql') {
$this->markTestSkipped();
}

Schema::createMaterializedView(
'active_users',
DB::table('users')->where('active', true)
);

$this->assertDatabaseCount('active_users', 1);

DB::table('users')->update(['active' => false]);

$this->assertDatabaseCount('active_users', 1);
}

public function testRenameView()
{
Schema::createView('active_users', DB::table('users')->where('active', true));
Expand Down Expand Up @@ -110,4 +128,22 @@ public function testGetViewColumnListing()

$this->assertSame(['active', 'created_at', 'id', 'name', 'updated_at'], $columns);
}

public function testRefreshMaterializedView()
{
if ($this->connection !== 'pgsql') {
$this->markTestSkipped();
}

Schema::createMaterializedView(
'active_users',
DB::table('users')->where('active', true)
);

DB::table('users')->update(['active' => false]);

Schema::refreshMaterializedView('active_users');

$this->assertDatabaseCount('active_users', 0);
}
}
6 changes: 5 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@

abstract class TestCase extends Base
{
protected string $connection;

protected function setUp(): void
{
$this->connection = getenv('DATABASE') ?: 'sqlite';

parent::setUp();

Schema::dropAllTables();
Expand Down Expand Up @@ -40,7 +44,7 @@ protected function getEnvironmentSetUp($app)

$app['config']->set('database.default', 'testing');

$app['config']->set('database.connections.testing', $config[getenv('DATABASE') ?: 'sqlite']);
$app['config']->set('database.connections.testing', $config[$this->connection]);
}

protected function getPackageProviders($app)
Expand Down

0 comments on commit db9fc1a

Please sign in to comment.