Search by

avadim / manticore-query-builder-laravel

avadim

Native ManticoreSearch database driver for Laravel

Package info

github.com/aVadim483/manticore-query-builder-laravel

pkg:composer/avadim/manticore-query-builder-laravel

Statistics

Installs: 485

Dependents: 1

Suggesters: 0

Stars: 8

Open Issues: 0

v4.0.1 2026-10-27 15:30 UTC

README

English | Русский

GitHub Release tests

ManticoreSearch Database Driver for Laravel

A native Laravel Database driver for ManticoreSearch. Use Laravel's database manager, Query Builder, collections, pagination, query events and Schema Builder over PDO's MySQL protocol.

Version 4.0.1 is the stable native driver. See the 4.0.1 release notes for changes and limitations. The 3.x wrapper API and its dependency on avadim/manticore-query-builder-php have been removed. Existing applications should follow the 3.x migration guide.

Contents

Requirements

  • PHP 8.2+ and Laravel 11–13, subject to each framework version's PHP requirements.
  • PHP extensions pdo, pdo_mysql and json.
  • ManticoreSearch 28.6.6+ with the MySQL protocol enabled, normally on port 9306.
  • Buddy for AUTOCOMPLETE; the KNN library and a vector index for KNN queries.

The supported server floor is 28.6.6. CI tests its official image pinned by digest and the moving latest image; see the server compatibility policy. Support for older servers, Lumen and general Eloquent compatibility is not established.

Installation

To install the stable 4.x driver:

composer require "avadim/manticore-query-builder-laravel:^4.0"

To test subsequent development changes:

composer require avadim/manticore-query-builder-laravel:dev-dev-4.x

The migration guide has been exercised on a Laravel 12 application with its original documents preserved. Other applications require their own checks.

Laravel discovers avadim\Manticore\Laravel\ServiceProvider automatically. If package discovery is disabled, register that provider in bootstrap/providers.php. DatabaseServiceProvider remains an alternative registration entry point for applications that used the native driver earlier in 4.x development. Registering either provider is sufficient. There is no ManticoreDb alias or package configuration to publish.

Configuration

Add this entry to connections in config/database.php:

'manticore' => [
    'driver' => 'manticore',
    'host' => env('MANTICORE_HOST', '127.0.0.1'),
    'port' => env('MANTICORE_PORT', 9306),
    'username' => env('MANTICORE_USER'),
    'password' => env('MANTICORE_PASS'),
    'timeout' => env('MANTICORE_TIMEOUT', 5),
    'prefix' => env('MANTICORE_PREFIX', ''),
    'cast_attributes' => true,
    'options' => [],
],

Keep the application's primary database as its default and use DB::connection('manticore') explicitly. Additional connection names can use the same manticore driver with their own settings. The driver reads no config/manticore.php, MANTICORE_CONNECTION, force_prefix or lazy_insert_ids. Laravel always prefixes builder table names; use products, without ?. Raw SQL is not rewritten. PDO emulated prepares must stay enabled.

Use DB::listen() for logging and DB::purge() / DB::reconnect() for worker connections. See the migration guide for logging and schema-cache lifecycle examples.

Queries and results

use Illuminate\Support\Facades\DB;

$db = DB::connection('manticore');
$query = $db->table('products')->select('id', 'title')->match('galaxy')
    ->where('price', '>', 100)->orderBy('id');

$rows = $query->get();                 // Collection<stdClass>
$first = (clone $query)->first();       // stdClass|null
$title = $first?->title;
$page = (clone $query)->paginate(20);
$lazy = (clone $query)->lazyById(500);   // LazyCollection<stdClass>

Query Builder select() returns the builder and configures columns. Connection $db->select($sql, $bindings) executes SQL and returns array<stdClass> without builder casts. get(['id', 'title']) accepts an array; a comma-separated string is not a column list. Rows use object properties. To get arrays, use $rows->map(static fn ($row) => (array)$row)->all().

Use whereFullText() for text input and an explicit field scope. It returns the builder; get(), first() or search() executes the query. Search operators are escaped by default.

$rows = $db->table('products')->whereFullText('title', 'galaxy phone')->get();
$rows = $db->table('products')->whereFullText(['title', 'body'], 'galaxy')
    ->where('stock', '>', 0)->get();

Repeated calls produce one MATCH. Text OR groups use orWhereFullText(); OR between text and attributes is unsupported. See full-text examples and options.

JSON filters use Laravel's familiar syntax:

$rows = $db->table('products')->where('details->color', 'red')
    ->whereJsonContains('details->tags', 'sale')->whereJsonLength('details->tags', '>=', 2)->get();

See JSON methods and Manticore-specific semantics.

cursor() returns a LazyCollection from one SELECT. lazy() reads offset pages; lazyById() walks an ID boundary. cursorPaginate() supports selected integer/bool/timestamp keys with ID as the final tie-breaker. Floats, computed keys, KNN and grouped/faceted cursor pagination are rejected. See the full driver reference for exact totals and streaming limits.

Writes and transactions

$id = $db->table('products')->insertGetId(['title' => 'Galaxy', 'price' => 120.5]);
$db->table('products')->where('id', $id)->update(['price' => 99.5]);
$db->table('products')->replace(['id' => $id, 'title' => 'Galaxy phone', 'price' => 99.5]);
$db->table('products')->where('id', $id)->delete();

$db->transaction(function () use ($db) {
    $db->table('products')->insert(['id' => 42, 'title' => 'Phone', 'price' => 50]);
});

insert() / replace() return bool, update() / delete() return affected counts, and insertGetId() / replaceGetId() return int|string. Put explicit IDs in the document. SQL failures throw Illuminate\Database\QueryException; zero affected rows can be successful. REPLACE replaces the entire document, resetting omitted attributes, and supports full-text changes. Nested transactions, DDL inside transactions and retry counts other than one are rejected. Writes are never automatically replayed after a lost response.

Search extensions

$result = $db->table('products')->match('galaxy')->select('id', 'title')
    ->withScore()->fieldWeights(['title' => 10])->ranker('bm25')
    ->withHighlight(['title'])
    ->facet('brand', 'brands', 10)->limit(20)->search();

$rows = $result->rows();
$total = $result->meta()->totalFound();
$brands = $result->facet('brands');

search() captures rows, metadata and facets before query listeners. get() fetches rows only. Other extensions include option(), knn(), MVA ANY/ALL, geo distance, and connection-level keywords(), suggest(), qsuggest(), snippets(), autocomplete() and generic call(). See the reference for signatures and server requirements.

The hybrid search guide progresses from a minimal call to advanced options. hybridSearch() returns HybridResult, hybridGet() returns Collection<stdClass>, and hybridFirst() returns stdClass|null. These execute immediately; the alpha.2 hybrid(...)->search() call is removed. knn()->match() retains intersection semantics. Capability probes and RT replacement are documented separately; replacement requires maintenance and does not promise zero downtime.

Schema and migrations

use avadim\Manticore\Laravel\Database\ManticoreBlueprint;
use Illuminate\Support\Facades\Schema;

Schema::connection('manticore')->create('products', function (ManticoreBlueprint $table) {
    $table->id();
    $table->text('title');
    $table->string('brand');
    $table->float('price');
    $table->json('details');
    $table->multi('tags');
});

CREATE, DROP and ALTER ADD/DROP use Laravel Schema Builder, including multi64(), floatVector() and CREATE setting(). Unsupported schema operations fail explicitly. Extended ALTER supports settings changes/inspection, int-to-bigint change() and table rename() with Buddy. Keep the migration repository in the primary SQL database; select Manticore inside individual migrations. DDL is not transactional. See the migration example.

Differences from Laravel Query Builder

The complete API profile and frozen contracts cover every reviewed public method, explicit rejections, argument defaults, results and exceptions.

The select(), get(), first() and lazy() contracts are preserved: a builder, collection, object/null and lazy collection respectively. The differences below matter when porting queries from Laravel's standard SQL drivers. Standard Query Builder capabilities also depend on the selected database; see Laravel's reference.

Operation Standard Laravel Query Builder This Manticore driver
Substring matching whereLike() or where('name', 'like', '%text%') use SQL LIKE LIKE in WHERE is unsupported. Use match() with full-text index rules for TEXT, or REGEX() through a SELECT alias for STRING. Their search semantics differ
Full-text search whereFullText() with database-specific options whereFullText() / orWhereFullText() compile to one MATCH, escape operators by default and accept only the escape option. Text/attribute OR is unsupported; match() accepts native expressions
JSON predicates details->color syntax and whereJson*() helpers on supported databases Arrow paths and whereJson*() are supported. Contains/overlaps test array elements; key checks treat JSON null as absent. See JSON semantics
Reading JSON Query Builder returns PDO values without automatically decoding JSON into PHP arrays By default, the builder reads DESCRIBE and converts JSON to arrays; cast_attributes => false disables conversion
get() / cursor() without limit() No row-count limit is added to the SQL Manticore defaults to 20 returned rows. Set limit() or use lazy() / chunk() for batched traversal
JOIN, UNION, DISTINCT, WHERE EXISTS, locks Methods expose SQL constructs supported by the selected database The current driver rejects these constructs
Updating text with update() Updates the specified text columns Full-text fields require replace(), which replaces the whole document and resets omitted fields
upsert() Native insert/update by unique keys under the SQL driver's rules Emulated lookup + INSERT/REPLACE; non-updated fields are preserved. Writes share one transaction, but lookups have no isolation and arbitrary lookup attributes have no uniqueness constraint
Writes inside a transaction INSERT, UPDATE and DELETE can be rolled back on transactional SQL tables Manticore transactions cover INSERT/REPLACE/DELETE; UPDATE is not rolled back. Driver nesting, savepoints and retries are rejected
cursorPaginate() Builds cursor boundaries from selected ordering columns under Laravel's constraints Only selected integer/bool/timestamp keys ending in ID are supported; KNN, groups, facets and float/string/JSON/MVA/computed keys are rejected

LIKE alternatives follow Manticore field types and REGEX. See also result limits, transactions and driver restrictions.

Use bound parameters with whereRaw() / selectRaw(); DB::raw() is for trusted SQL. Escape literal MATCH text with avadim\Manticore\Laravel\Database\ManticoreQueryBuilder::escapeMatch(). Build a single MATCH expression per query.

Testing and benchmarks

# From a repository clone
composer install
composer test

Integration tests use MANTICORE_TEST_HOST / MANTICORE_TEST_PORT (127.0.0.1:9306). Set MANTICORE_TEST_BUDDY=1 to require AUTOCOMPLETE. Tests skip unavailable server features locally; CI uses --fail-on-skipped. The default bootstrap rejects loading standalone PHP-builder classes. Tests also require pdo_sqlite for the primary migration repository. See verification instructions for isolated production consumers and test matrices.

Benchmarks compare the native driver with PDO and the official HTTP client. Performance covers current bulk-write comparisons and earlier upsert, read, hybrid and worker measurements, with explicit versions and workload limits.