Skip to content

[13.x] Fix MariaDB vector distance SQL and add AsVector Eloquent cast - #61337

Merged
taylorotwell merged 3 commits into
laravel:13.xfrom
eas4ai:mariadb-vector-cast
Aug 25, 2026
Merged

[13.x] Fix MariaDB vector distance SQL and add AsVector Eloquent cast#61337
taylorotwell merged 3 commits into
laravel:13.xfrom
eas4ai:mariadb-vector-cast

Conversation

@eas4ai

@eas4ai eas4ai commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #61250, which routed the vector query builder methods through the grammar and added MariaDB support via vec_distance_cosine().

1. Bug fix: the MariaDB SQL from #61250 is rejected by the server

MariaDB's VEC_DISTANCE_* functions only accept VECTOR arguments. The query builder binds the vector as JSON text, so vec_distance_cosine(col, ?) fails on a real server for every whereVectorSimilarTo / whereVectorDistanceLessThan / orderByVectorDistance / selectVectorDistance call (reproduced on MariaDB 11.8.9, with both native and emulated prepares):

SQLSTATE[HY000]: General error: 4079 Illegal parameter data type varchar for operation 'VEC_DISTANCE_COSINE'

MariaDbGrammar::compileVectorDistanceExpression() now emits vec_distance_cosine(col, vec_fromtext(?)). The bindings are unchanged (still JSON text), and EXPLAIN confirms the MHNSW VECTOR INDEX is still used for order by vec_distance_cosine(..., vec_fromtext(?)).

2. New AsVector Eloquent cast

Vector columns had no first-party cast. On MariaDB the array cast cannot work at all: the column is returned as little-endian float32 bytes, and a JSON string bound to a VECTOR column is rejected with 1292 Incorrect vector value (raw packed bytes bound as a parameter are rejected too, regardless of PDO::PARAM_LOB).

Illuminate\Database\Eloquent\Casts\AsVector is driver-portable:

  • get: decodes MariaDB's binary format (unpack('g*')), pgvector / vec_totext() JSON text, and a not-yet-persisted MariaDB value (so $model->embedding works right after create()).
  • set: accepts an array or Arrayable (e.g. a Collection) of floats. On MariaDB it stores a vec_fromtext('[...]') expression, since the conversion has to happen server-side; on other drivers it stores the JSON string (which pgvector accepts). The inlined JSON only ever contains digits, ., -, e, , and brackets (json_encode(..., JSON_THROW_ON_ERROR) rejects NaN/Inf), so it is safe to inline.
Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->vector('embedding', 768);
    $table->vectorIndex('embedding');
});

class Document extends Model
{
    protected $casts = ['embedding' => AsVector::class];
}

Document::create(['embedding' => $embedding]);

Document::query()->whereVectorSimilarTo('embedding', $query, minSimilarity: 0.7)->limit(10)->get();

Test plan

  • tests/Database/DatabaseQueryBuilderTest.php — MariaDB vector assertions updated to vec_fromtext(?).
  • tests/Database/DatabaseEloquentAsVectorCastTest.php — new unit tests for the cast against the MariaDB and Postgres grammars (binary/text decoding, vec_fromtext vs JSON storage, Arrayable input, null handling, invalid input, read-back before save).
  • tests/Integration/Database/MariaDb/EloquentVectorTest.php — new end-to-end test (vector() + vectorIndex() migration, create/read/update through the cast, selectVectorDistance + whereVectorSimilarTo). Gated with #[RequiresDatabase('mariadb', '>=11.7.0')] since CI runs mariadb:10; passes locally against MariaDB 11.8.9.
  • vendor/bin/phpunit tests/Database (2842 tests)
  • vendor/bin/pint --test
MariaDB's VEC_DISTANCE_* functions only accept VECTOR arguments, so the
JSON text bound by the vector query builder methods must be converted
with vec_fromtext(). Without it every whereVectorSimilarTo /
whereVectorDistanceLessThan / orderByVectorDistance / selectVectorDistance
call fails on MariaDB with "4079 Illegal parameter data type varchar for
operation 'VEC_DISTANCE_COSINE'".

Adds an AsVector cast so Eloquent models can read and write vector
columns: it decodes MariaDB's little-endian float32 bytes as well as
pgvector / vec_totext() JSON text, and writes vec_fromtext('[...]') on
MariaDB (which rejects text or raw bytes bound to a VECTOR column) or a
plain JSON string elsewhere.
@eas4ai
eas4ai force-pushed the mariadb-vector-cast branch from 7be4082 to 1b45538 Compare August 25, 2026 17:55
@eas4ai

eas4ai commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Waiting on #61335 merge to fix CI failures not related to this PR

@taylorotwell
taylorotwell merged commit cb89165 into laravel:13.x Aug 25, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants