Skip to content

Laravel Batch v3.2.0

Choose a tag to compare

@mavinoo mavinoo released this 05 Oct 06:29
· 10 commits to master since this release
6a81bf4

New bulk operations for large tables and files, JSON column updates, and two JSON bug fixes.
Everything is additive, so upgrading from 3.1 needs no code changes:

composer update mavinoo/laravel-batch

Added

Update keys inside JSON columns (#64, #83, #111)

Batch::update(new User, [
    ['id' => 1, 'settings->theme' => 'dark', 'settings->notify->email' => false],
]);
  • Works in every update method, on MySQL, MariaDB, PostgreSQL and SQLite.
  • Other keys are kept, missing objects are created, and values keep their JSON type.
  • updated_at only changes when the document changes.
  • Arrays and collections for columns the model casts to JSON (array, json, collection,
    AsCollection, ...) are encoded the way the model stores them, in update, insert and upsert.

insertGetIds(): insert rows and get their ids (#48)

$ids = Batch::insertGetIds(new User, $rows); // [101, 102, ...] in row order
  • Checked with 8 concurrent writers and 78,720 rows on MySQL 8.4, MariaDB, PostgreSQL and SQLite,
    with 0 wrong ids.

import(): import files and iterables with constant memory

Batch::import(new User, storage_path('users.csv'), ['mode' => 'upsert', 'uniqueBy' => ['email']]);
  • Reads CSV, TSV and JSON Lines files (also .gz), open streams, or any iterable such as
    Model::cursor().
  • Supports column mapping, null values, transforms, and progress and error callbacks.
500,000-row CSV import() array + insertRows()
Memory 7 MB 765 MB
Time 30–45% faster

splitFile(): split large files

Batch::splitFile($path, lines: 50000); // users-001.csv, users-002.csv, ...
  • Splits by number of records and / or size.
  • Never cuts a record or a multi-line CSV field in half, and repeats the header in every part.

deleteInChunks(), updateInChunks(), archive()

Log::where('created_at', '<', now()->subYear())->deleteInChunks(10000);
User::where('active', false)->updateInChunks(['status' => 'archived']);
Order::where('created_at', '<', '2020-01-01')->archiveTo('orders_archive');
  • Each runs a chunk at a time, in primary key order, so no lock is held for long.
  • Deleting 200k rows on MySQL 8.4: the longest chunk took 0.10 s, against 0.8 s for one DELETE.
  • archive() copies and deletes each chunk in one transaction.

sync(): make a table match a list

Product::where('supplier_id', 5)->syncRows($feedRows, ['sku']);
// ['inserted' => 120, 'updated' => 4800, 'deleted' => 35]
  • Inserts, updates and deletes in one transaction.
  • The database compares the keys itself, so its collation decides which are equal. On MySQL,
    "ABC" and "abc" are the same key.
  • Soft-deleted rows that are in the list are restored.
  • Syncing 100,000 rows takes 1.4–4.8 s with about 15 MB of memory.

Fixed

  • Updating a PostgreSQL json column failed with operator does not exist: json <> unknown.
  • MySQL json columns touched updated_at even when their value didn't change.

Also

  • 219 tests on SQLite, MySQL, MariaDB and PostgreSQL, PHP 8.1 – 8.5 and Laravel 10 – 13.
  • PHPStan level 9 with Larastan.

Full changelog: https://github.com/mavinoo/laravelBatch/blob/master/CHANGELOG.md
Pull request: #126