Skip to content

feat: add surreal schema driver foundation#114

Open
ibourgeois wants to merge 3 commits intomainfrom
codex/feat-surreal-migration-driver
Open

feat: add surreal schema driver foundation#114
ibourgeois wants to merge 3 commits intomainfrom
codex/feat-surreal-migration-driver

Conversation

@ibourgeois
Copy link
Contributor

Summary

  • register a first Laravel-compatible database connection for schema work
  • add a narrow Surreal schema grammar, builder, and manager for common Katra field types
  • prove the flow with a real Surreal-backed Pest feature test and document the current scope

Testing

  • vendor/bin/pint --dirty --format agent
  • php artisan test --compact tests/Feature/SurrealWorkspaceModelTest.php tests/Feature/SurrealSchemaDriverTest.php

Closes #113

Copilot AI review requested due to automatic review settings March 23, 2026 02:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a first Laravel Schema/Migration “driver” foundation for SurrealDB so migrations can target Surreal-backed application data via Laravel’s Schema API.

Changes:

  • Registers a new surreal database driver/connection for schema operations.
  • Adds Surreal schema grammar/builder/manager to support create/add/drop columns and create/drop tables for a narrow set of column types.
  • Adds a Surreal-backed Pest feature test and documents current scope/limitations in the README.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
config/database.php Adds a surreal connection entry pointing at the new surreal driver.
app/Providers/AppServiceProvider.php Registers the surreal database driver via DatabaseManager::extend().
app/Services/Surreal/Schema/SurrealSchemaConnection.php New Laravel Connection implementation used for Surreal schema operations.
app/Services/Surreal/Schema/SurrealSchemaBuilder.php New schema builder that delegates table/column introspection & drops to Surreal.
app/Services/Surreal/Schema/SurrealSchemaGrammar.php New schema grammar compiling Laravel schema commands into Surreal DEFINE/REMOVE statements.
app/Services/Surreal/Schema/SurrealSchemaManager.php New manager running INFO queries and executing schema statements through the Surreal CLI.
tests/Feature/SurrealSchemaDriverTest.php End-to-end test proving create/alter/drop via Schema::connection('surreal').
README.md Documents how to use Surreal-backed migrations and clarifies current scope.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

$config,
);
}

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SurrealSchemaConnection is constructed with an in-memory SQLite PDO. That means any accidental use of DB::connection('surreal')->select()/insert()/transaction() (or other non-schema operations that hit the base Connection implementation) would silently run against SQLite instead of SurrealDB, producing incorrect behavior without obvious failures. Consider explicitly disabling query/transaction APIs on this connection (override the relevant methods to throw a clear RuntimeException), and/or override getDriverName() to surreal to avoid Laravel treating it as SQLite anywhere internally.

Suggested change
public function getDriverName(): string
{
return 'surreal';
}
/**
* SurrealSchemaConnection is schema-only; data queries are not supported.
*
* @param string $query
* @param array<int, mixed> $bindings
* @param bool $useReadPdo
* @return array<int, mixed>
*/
public function select($query, $bindings = [], $useReadPdo = true)
{
throw new \RuntimeException('SurrealSchemaConnection does not support select queries. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; data queries are not supported.
*
* @param string $query
* @param array<int, mixed> $bindings
* @param bool $useReadPdo
* @return \Generator
*/
public function cursor($query, $bindings = [], $useReadPdo = true)
{
throw new \RuntimeException('SurrealSchemaConnection does not support cursors. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; data queries are not supported.
*
* @param string $query
* @param array<int, mixed> $bindings
* @return bool
*/
public function insert($query, $bindings = [])
{
throw new \RuntimeException('SurrealSchemaConnection does not support insert queries. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; data queries are not supported.
*
* @param string $query
* @param array<int, mixed> $bindings
* @return int
*/
public function update($query, $bindings = [])
{
throw new \RuntimeException('SurrealSchemaConnection does not support update queries. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; data queries are not supported.
*
* @param string $query
* @param array<int, mixed> $bindings
* @return int
*/
public function delete($query, $bindings = [])
{
throw new \RuntimeException('SurrealSchemaConnection does not support delete queries. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; transactions are not supported.
*
* @param \Closure $callback
* @param int $attempts
* @return mixed
*/
public function transaction(\Closure $callback, $attempts = 1)
{
throw new \RuntimeException('SurrealSchemaConnection does not support transactions. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; transactions are not supported.
*/
public function beginTransaction()
{
throw new \RuntimeException('SurrealSchemaConnection does not support transactions. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; transactions are not supported.
*/
public function commit()
{
throw new \RuntimeException('SurrealSchemaConnection does not support transactions. Use SurrealConnection for data access.');
}
/**
* SurrealSchemaConnection is schema-only; transactions are not supported.
*
* @param int|null $toLevel
*/
public function rollBack($toLevel = null)
{
throw new \RuntimeException('SurrealSchemaConnection does not support transactions. Use SurrealConnection for data access.');
}

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +145
function retryStartingSurrealSchemaServer(SurrealCliClient $client, string $storagePath, int $attempts = 3): array
{
$lastException = null;

for ($attempt = 1; $attempt <= $attempts; $attempt++) {
$port = reserveSurrealSchemaPort();
$endpoint = sprintf('ws://127.0.0.1:%d', $port);
$process = $client->startLocalServer(
bindAddress: sprintf('127.0.0.1:%d', $port),
datastorePath: $storagePath,
username: 'root',
password: 'root',
storageEngine: 'surrealkv',
);

if ($client->waitUntilReady($endpoint)) {
return [
'endpoint' => $endpoint,
'port' => $port,
'process' => $process,
];
}

$process->stop(1);
$lastException = new RuntimeException(sprintf('SurrealDB did not become ready on %s.', $endpoint));
}

throw $lastException ?? new RuntimeException('Unable to start the SurrealDB schema test runtime.');
}

function reserveSurrealSchemaPort(): int
{
$socket = stream_socket_server('tcp://127.0.0.1:0', $errorCode, $errorMessage);

if ($socket === false) {
throw new RuntimeException(sprintf('Unable to reserve a free TCP port: %s (%d)', $errorMessage, $errorCode));
}

$address = stream_socket_get_name($socket, false);

fclose($socket);

if ($address === false) {
throw new RuntimeException('Unable to determine the reserved TCP port.');
}

$port = (int) ltrim((string) strrchr($address, ':'), ':');

if ($port <= 0) {
throw new RuntimeException(sprintf('Unable to parse the reserved TCP port from [%s].', $address));
}

return $port;
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test duplicates the same port-reservation + retry-start-server helpers already present in other Surreal feature tests (e.g. retryStartingWorkspaceServer / reserveWorkspacePort). Consider extracting these helpers into a shared test support file/trait (or a Pest helper) to reduce duplication and keep the startup logic consistent across tests.

Copilot uses AI. Check for mistakes.
@ibourgeois ibourgeois force-pushed the codex/feat-surreal-migration-driver branch 2 times, most recently from e9ee757 to 0045594 Compare March 23, 2026 06:37
@ibourgeois ibourgeois force-pushed the codex/feat-surreal-migration-driver branch from 0045594 to 8d9664b Compare March 23, 2026 06:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +106 to +119
config()->set('database.default', 'surreal');
config()->set('database.migrations.connection', null);

config()->set('surreal.host', '127.0.0.1');
config()->set('surreal.port', $server['port']);
config()->set('surreal.endpoint', $server['endpoint']);
config()->set('surreal.username', 'root');
config()->set('surreal.password', 'root');
config()->set('surreal.namespace', 'katra');
config()->set('surreal.database', 'schema_driver_migrate_test');
config()->set('surreal.storage_engine', 'surrealkv');
config()->set('surreal.storage_path', $storagePath);
config()->set('surreal.runtime', 'local');
config()->set('surreal.autostart', false);
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test mutates global config (database.default and database.migrations.connection) but never restores the previous values. Because Pest runs tests in the same process, this can leak into later tests (e.g. those using RefreshDatabase) and cause them to run against the Surreal connection unexpectedly. Capture the original values before overriding and restore them in the finally block (or in an afterEach).

Copilot uses AI. Check for mistakes.
return parent::repositoryExists();
}

return $this->schemaManager()->hasTable($this->table);
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repositoryExists() uses $this->table directly, while the rest of the Surreal-specific statements go through normalizedTable() (which also validates the identifier). Using the raw table name here can lead to inconsistent behavior and delayed failures when a custom migrations table name contains unsupported characters. Consider using normalizedTable() here as well so validation is consistent and errors surface early.

Suggested change
return $this->schemaManager()->hasTable($this->table);
return $this->schemaManager()->hasTable($this->normalizedTable());

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add a SurrealDB Laravel schema and migration driver foundation

2 participants