laravel-postgresql-enhanced

repository·master·Indexed 21 days ago

https://github.com/tpetry/laravel-postgresql-enhanced

A package that enhances Laravel's PostgreSQL driver with support for advanced features including zero-downtime migrations, materialized views, database functions, triggers, domain types, and advanced indexing (concurrent, partial, and fulltext). It also provides support for Common Table Expressions (CTE), Lateral Subquery Joins, and specialized extensions like Timescale.

Tokens
17.4K
Snippets
70
Records
74
Agent score
76%

What's inside laravel-postgresql-enhanced

  1. Overview of enhanced PostgreSQL features

    master

    The laravel-postgresql-enhanced package extends Laravel's database capabilities to leverage specific PostgreSQL features. Key areas of enhancement include:

    • Migrations: Support for Zero Downtime Migrations, Extensions, Functions, Triggers, Views (including Materialized Views), Foreign Keys, and advanced Indexing (Concurrent, Partial, Fulltext, etc.).
    • Column & Table Options: Advanced types (Arrays, Ranges, Vector, Hstore, etc.) and table configurations (Unlogged, Storage Parameters).
    • Query Builder: Support for EXPLAIN, Common Table Expressions (CTE), Lateral Subquery Joins, and returning data from modified rows.
    • Eloquent: Enhanced Casts, automatic data refreshing on save, and custom date formats.
    • Extensions: Support for specialized extensions like Timescale.
  2. Automatically refresh computed columns using RefreshDataOnSave

    master

    When using Laravel's storedAs($expression) or database triggers to manage dynamically computed columns, standard Eloquent behavior only updates the primary key after a save, leaving computed properties out of sync.

    To ensure your model instance automatically reflects changes to computed columns (via PostgreSQL RETURNING statements) immediately after saving, use the RefreshDataOnSave trait.

    use Illuminate\Database\
    Eloquent\Model;
    use Tpetry\PostgresqlEnhanced\Eloquent\Concerns\RefreshDataOnSave;
    
    class Example extends Model
    {
        use RefreshDataOnSave;
    
        // Computed columns like 'text_uppercase' will now be automatically
        // updated in the model instance after ->save() is called.
    }
  3. Configure Unlogged Tables

    master

    Unlogged tables skip some durability requirements to provide a significant speed boost for high-write workloads. Data in unlogged tables is lost during a server crash but is preserved during a clean shutdown. This is ideal for temporary data like sessions.

    Use the unlogged() method on the table blueprint to activate or deactivate this mode.

    Schema::table('sessions', function (Blueprint $table): void {
        // make the table unlogged
        $table->unlogged();
    
        // make the table crash-safe again
        $table->unlogged(false);
    });
  4. Handle PostgreSQL date formats with AutomaticDateFormat traits

    master

    PostgreSQL supports extended date formats like timestampTz (time zone aware) and millisecond precision. Standard Eloquent models may not handle these correctly without manual $dateFormat configuration. Use these traits to automate the process:

    1. AutomaticDateFormat: Use this when your table contains ->timestampTz() columns.
    2. AutomaticDateFormatWithMilliseconds: Use this when your table contains columns that store milliseconds (via ->timestamp() or ->timestampTz()).
    CAUTION

    If you mix columns with and without milliseconds in the same table, PostgreSQL may round values instead of truncating them. This can cause timestamps without milliseconds to appear as if they are in the future.

    use Illuminate\Database\Eloquent\Model;
    use Tpetry\PostgresqlEnhanced\Eloquent\Concerns\AutomaticDateFormat;
    
    class Example extends Model
    {
        use AutomaticDateFormat;
    }
  5. Create Concurrent Indexes

    master

    To prevent creating an index from blocking all SQL queries on a large table, you can use the concurrently() method.

    Important: Creating a concurrent index must be run outside of a transaction. In a Laravel migration, you must set $withinTransaction = false.

    return new class extends Migration
    {
        public $withinTransaction = false;
    
        public function up(): void
        {
            Schema::table('blog_visits', function (Blueprint $table) {
                $table->index(['url', 'ip_address'])->concurrently();
            });
        }
    };
  6. Use TimescaleDB features in migrations

    master

    The package provides enhanced support for TimescaleDB features within Laravel migrations. You can enable the extension using Schema::createExtensionIfNotExists('timescaledb') and then use the $table->timescale(...) method on a standard table blueprint or the Schema::continuousAggregate(...) method for continuous aggregates.

    Important: Indexes are not automatically created when creating hypertables or continuous aggregates; you must define them manually in your migration.

    Supported TimescaleDB features include:

    • Hypertables: CreateHypertable and ChangeChunkTimeInterval.
    • Chunk Skipping: EnableChunkSkipping and DisableChunkSkipping.
    • Columnstore: EnableColumnstore, DisableColumnstore, CreateColumnstorePolicy, DropColumnstorePolicy, ConvertToColumnstore, and ConvertToRowstore.
    • Reordering: CreateReorderPolicy, CreateReorderPolicyByIndex, CreateReorderPolicyByUnique, DropReorderPolicy, and ReorderChunks.
    • Data Retention: CreateRetentionPolicy, DropRetentionPolicy, and DropChunks.
    • Tiered Storage: CreateTieringPolicy, DropTieringPolicy, TierChunks, and UntierChunks.
    • Continuous Aggregates: CreateRefreshPolicy, DropRefreshPolicy, and RefreshData.
    use Illuminate\Database\Migrations\Migration;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\Actions\CreateColumnstorePolicy;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\Actions\CreateHypertable;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\Actions\CreateRefreshPolicy;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\Actions\CreateRetentionPolicy;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\Actions\EnableChunkSkipping;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\Actions\EnableColumnstore;
    use Tpetry\PostgresqlEnhanced\Schema\Timescale\CaggBlueprint;
    use Tpetry\PostgresqlEnhanced\Support\Facades\Schema;
    use Tpetry\PostgresqlEnhanced\Schema\Blueprint;
    
    return new class extends Migration
    {
        public function up(): void
        {
            Schema::createExtensionIfNotExists('timescaledb');
    
            Schema::create('visits', function (Blueprint $table) {
                $table->identity();
                $table->bigInteger('website_id');
                $table->text('url');
                $table->float('duration');
                $table->timestampTz('created_at');
    
                $table->primary(['id', 'created_at']);
                $table->index(['website_id', 'created_at']);
    
                $table->timescale(
                    new CreateHypertable('created_at', '1 day'),
                    new EnableColumnstore(segmentBy: 'website_id'),
                    new CreateColumnstorePolicy('3 days'),
                    new CreateRetentionPolicy('1 year'),
                    new EnableChunkSkipping('id'),
                );
            });
    
            Schema::continuousAggregate('visits_agg', function(CaggBlueprint $table) {
                $table->as("\n                SELECT\n                    time_bucket('1 hour', created_at) AS bucket,\n                    website_id,\n                    url,\n                    SUM(duration) AS duration\n                FROM visits\n                GROUP BY bucket, website_id, url\n            ");
                $table->realtime();
                $table->index(['website_id','url']);
    
                $table->timescale(
                    new CreateRefreshPolicy('5 minutes', '1 days', '2 hours'),
                    new EnableColumnstore(),
                    new CreateColumnstorePolicy('2 days'),
                );
            });
        }
    };
  7. Create and manage Domain Types

    master

    Domain types allow you to create application-specific types by aliasing an existing base type and adding an optional SQL validation condition. This is useful for enforcing rules like non-negative prices or specific formats (e.g., license plates) at the database level.

    Create a Domain Type

    Use Schema::createDomain() with the name, base type, and an optional condition. The condition can be a raw SQL string or a closure using a Builder instance.

    Use Domain Types

    In migrations, use the domain() method on the blueprint, passing the column name and the name of the domain type.

    Altering Domain Types

    You cannot change the base type of a domain, but you can update its validation condition using Schema::changeDomainConstraint() by passing a new SQL string or a closure. Passing null removes the constraint.

    Dropping Domain Types

    To remove a domain, first drop all columns using it. Then use Schema::dropDomain() or Schema::dropDomainIfExists(). You can drop multiple domains at once by passing multiple names.

    // Create a domain with a closure-based constraint
    Schema::createDomain('price', 'numeric(9,2)', fn (Builder $query) => $query->where('VALUE', '>=', 0));
    
    // Use the domain in a migration
    Schema::create('products', function (Blueprint $table): void {
      $table->domain('item_price', 'price');
    });
    
    // Change the constraint
    Schema::changeDomainConstraint('price', 'VALUE > 0');
    
    // Drop domains
    Schema::dropDomain('price', 'license_plate');
  8. Configure PHPStan for enhanced PostgreSQL support

    master

    This package provides custom PHPStan extensions to ensure full static analysis support for the added PostgreSQL features.

    If you are using phpstan/extension-installer, the extensions are automatically recognized. If not, you must manually include the package's PHPStan configuration in your phpstan.neon file.

    includes:
        - ./vendor/nunomaduro/larastan/extension.neon
        - ./vendor/tpetry/laravel-postgresql-enhanced/phpstan-extension.neon
  9. Perform zero-downtime migrations

    master

    For applications requiring 24/7 availability, you can mark migrations as zero-downtime to prevent long-running schema changes from locking tables indefinitely. By using the ZeroDowntimeMigration trait, the migration will be cancelled and the schema reset to its original state if it exceeds a specified time limit.

    To configure timeouts, you can define:

    • private float $timeout: A shared timeout for both up() and down() methods.
    • private float $timeoutUp: A specific timeout for the up() method.
    • private float $timeoutDown: A specific timeout for the down() method.
    use Illuminate\Database\Migrations\Migration;
    use Tpetry\PostgresqlEnhanced\Schema\Blueprint;
    use Tpetry\PostgresqlEnhanced\Schema\Concerns\ZeroDowntimeMigration;
    use Tpetry\PostgresqlEnhanced\Support\Facades\Schema;
    
    class Test123 extends Migration
    {
        use ZeroDowntimeMigration;
    
        private float $timeoutUp = 5.0;
    
        /**
         * Run the migrations.
         */
        public function up(): void
        {
            Schema::table('user', function (Blueprint $table) {
                $table->string('name', 128)->change();
            });
        }
    
        /**
         * Reverse the migrations.
         */
        public function down(): void
        {
            Schema::table('user', function (Blueprint $table) {
                $table->string('name', 32)->change();
            });
        }
    }
  10. Configure Table Storage Parameters

    master

    Fine-tune tables for specific workloads using storage parameters via the with() method on the blueprint. Common use cases include:

    • fillfactor: Tuning for faster UPDATE operations (e.g., using HOT updates).
    • autovacuum_analyze_scale_factor: Tuning statistics generation for tables with millions of rows.
    Schema::table('sessions', function (Blueprint $table): void {
        $table->with([
            'autovacuum_analyze_scale_factor' => 0.02,
            'fillfactor' => 90,
        ]);
    });
  11. Enable IDE Autocomplete for PostgreSQL features

    master

    Because this package adds functionality beyond Laravel's standard database driver, IDEs may not automatically recognize the new methods.

    To enable autocomplete in PhpStorm using the Laravel Idea plugin, run the following action:

    Laravel -> Code Generation -> Generate Helper Code (Eloquent, Macro, Facades, etc.).

    This utilizes the barryvdh/laravel-ide-helper logic to detect the enhanced PostgreSQL features.