Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 30 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ composer require utopia-php/query
- [Serializing an AST](#serializing-an-ast)
- [Walking and Rewriting](#walking-and-rewriting)
- [Builder Round-Trip](#builder-round-trip)
- [Wire Protocol Parsers](#wire-protocol-parsers)
- [SQL Parser](#sql-parser)
- [MySQL Parser](#mysql-parser)
- [PostgreSQL Parser](#postgresql-parser)
- [MongoDB Parser](#mongodb-parser)
- [Wire Protocol Classifiers](#wire-protocol-classifiers)
- [SQL Classifier](#sql-classifier)
- [MySQL Classifier](#mysql-classifier)
- [PostgreSQL Classifier](#postgresql-classifier)
- [MongoDB Classifier](#mongodb-classifier)
- [Compiler Interface](#compiler-interface)
- [Contributing](#contributing)
- [License](#license)
Expand Down Expand Up @@ -2627,7 +2627,7 @@ Column types map to BSON types: `string` → `string`, `integer`/`bigInteger`

## SQL Tokenizer and AST

Everything above generates SQL. This layer goes the other way: it takes existing SQL text and turns it into an inspectable, rewritable tree. Use it to validate columns against an allow-list, inject tenant predicates into queries you did not author, rename tables, or translate a statement from one dialect's quoting to another's.
Everything above generates SQL. This layer goes the other way: it takes existing SQL text and turns it into an inspectable, rewritable tree. (For merely deciding whether a query reads or writes, without building a tree, see [Wire Protocol Classifiers](#wire-protocol-classifiers).) Use it to validate columns against an allow-list, inject tenant predicates into queries you did not author, rename tables, or translate a statement from one dialect's quoting to another's.

```php
use Utopia\Query\Tokenizer\Tokenizer;
Expand Down Expand Up @@ -2735,27 +2735,29 @@ $rebuilt = Builder::fromAst($ast); // static
$rebuilt->build()->query; // SELECT `id`, `name` FROM `users` WHERE `status` IN (?)
```

## Wire Protocol Parsers
## Wire Protocol Classifiers

The `Parser` interface classifies raw database traffic into query types (`Read`, `Write`, `TransactionBegin`, `TransactionEnd`, `Transaction`, `Unknown`). This is useful for connection proxies, audit logging, and read/write splitting.
The `Classifier` interface sorts raw database traffic into query types (`Read`, `Write`, `TransactionBegin`, `TransactionEnd`, `Transaction`, `Unknown`). This is useful for connection proxies, audit logging, and read/write splitting.

```php
use Utopia\Query\Parser;
use Utopia\Query\Classifier;
use Utopia\Query\Type;
```

### SQL Parser
> **Not a parser.** A classifier reads a message's leading keyword (or, for document protocols, its first command name) and looks it up — it never builds a structure. To parse SQL text into a syntax tree you can inspect and rewrite, use [`AST\Parser`](#sql-tokenizer-and-ast) instead.

The abstract `Parser\SQL` class provides keyword-based classification for SQL dialects:
### SQL Classifier

The abstract `Classifier\SQL` class provides keyword-based classification for SQL dialects:

```php
use Utopia\Query\Parser\SQL;
use Utopia\Query\Classifier\SQL;

// Classify SQL text directly
$type = $parser->classifySQL('SELECT * FROM users'); // Type::Read
$type = $parser->classifySQL('INSERT INTO users ...'); // Type::Write
$type = $parser->classifySQL('BEGIN'); // Type::TransactionBegin
$type = $parser->classifySQL('COMMIT'); // Type::TransactionEnd
$type = $classifier->classifySQL('SELECT * FROM users'); // Type::Read
$type = $classifier->classifySQL('INSERT INTO users ...'); // Type::Write
$type = $classifier->classifySQL('BEGIN'); // Type::TransactionBegin
$type = $classifier->classifySQL('COMMIT'); // Type::TransactionEnd
```

Read keywords: `SELECT`, `SHOW`, `DESCRIBE`, `DESC`, `EXPLAIN`, `WITH` (when followed by a read), `TABLE`, `VALUES`.
Expand All @@ -2768,41 +2770,41 @@ Anything else — including `RENAME`, `REPLACE`, `LOAD`, `MERGE`, and `EXECUTE`

Special handling: `COPY` is classified based on direction (`FROM STDIN` = Write, `TO STDOUT` = Read).

### MySQL Parser
### MySQL Classifier

Parses MySQL wire protocol binary packets:

```php
use Utopia\Query\Parser\MySQL;
use Utopia\Query\Classifier\MySQL;

$parser = new MySQL();
$type = $parser->parse($rawPacketData); // Type::Read, Write, TransactionBegin, etc.
$classifier = new MySQL();
$type = $classifier->classify($rawPacketData); // Type::Read, Write, TransactionBegin, etc.
```

Recognizes `COM_QUERY` (`0x03`, classified via its SQL text), `COM_STMT_PREPARE` (`0x16`), `COM_STMT_EXECUTE` (`0x17`), `COM_STMT_SEND_LONG_DATA` (`0x18`), `COM_STMT_CLOSE` (`0x19`), and `COM_STMT_RESET` (`0x1A`). The prepared-statement commands are routed to the primary.

### PostgreSQL Parser
### PostgreSQL Classifier

Parses PostgreSQL wire protocol messages:

```php
use Utopia\Query\Parser\PostgreSQL;
use Utopia\Query\Classifier\PostgreSQL;

$parser = new PostgreSQL();
$type = $parser->parse($rawMessageData); // Type::Read, Write, TransactionBegin, etc.
$classifier = new PostgreSQL();
$type = $classifier->classify($rawMessageData); // Type::Read, Write, TransactionBegin, etc.
```

Handles message types `Q` (simple query, classified via its SQL text), `P` (parse), `B` (bind), and `E` (execute). Other message types, including terminate and startup messages, return `Type::Unknown`.

### MongoDB Parser
### MongoDB Classifier

Parses MongoDB OP_MSG binary protocol messages:

```php
use Utopia\Query\Parser\MongoDB;
use Utopia\Query\Classifier\MongoDB;

$parser = new MongoDB();
$type = $parser->parse($rawOpMsgData); // Type::Read, Write, TransactionBegin, etc.
$classifier = new MongoDB();
$type = $classifier->classify($rawOpMsgData); // Type::Read, Write, TransactionBegin, etc.
```

Extracts the command name from BSON documents and classifies:
Expand Down
10 changes: 10 additions & 0 deletions src/Query/AST/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@
use Utopia\Query\Tokenizer\Token;
use Utopia\Query\Tokenizer\TokenType;

/**
* Recursive-descent SQL parser.
*
* Consumes filtered {@see Token}s from a {@see \Utopia\Query\Tokenizer\Tokenizer}
* and builds a {@see Select} tree that can be inspected, rewritten via
* {@see Walker} and {@see Visitor}, and re-serialized by {@see Serializer}.
*
* Not to be confused with {@see \Utopia\Query\Classifier}, which only reads a
* message's leading keyword to route it to a primary or replica.
*/
class Parser
{
private const int MAX_DEPTH = 256;
Expand Down
24 changes: 24 additions & 0 deletions src/Query/Classifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Utopia\Query;

/**
* Wire Protocol Query Classifier
*
* Classifies database wire protocol messages as Read, Write, Transaction, or Unknown
* to enable routing queries to appropriate primary/replica backends.
*
* This does not parse queries: implementations read the leading keyword (or, for
* document protocols, the first command name) and look it up. For a structural
* parse of SQL text into a syntax tree, see {@see AST\Parser}.
*/
interface Classifier
{
/**
* Classify a raw wire protocol message
*
* @param string $data Raw protocol message bytes
* @return Type Classification result
*/
public function classify(string $data): Type;
}
24 changes: 4 additions & 20 deletions src/Query/Parser/MongoDB.php → src/Query/Classifier/MongoDB.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

namespace Utopia\Query\Parser;
namespace Utopia\Query\Classifier;

use Utopia\Query\Parser;
use Utopia\Query\Classifier;
use Utopia\Query\Type;

/**
Expand All @@ -26,7 +26,7 @@
* - TransactionBegin: startTransaction flag present
* - TransactionEnd: commitTransaction or abortTransaction command
*/
class MongoDB implements Parser
class MongoDB implements Classifier
{
/**
* Read command names (lowercase)
Expand Down Expand Up @@ -99,7 +99,7 @@ class MongoDB implements Parser
*/
private const MIN_MSG_SIZE = 26;

public function parse(string $data): Type
public function classify(string $data): Type
{
$len = \strlen($data);
if ($len < self::MIN_MSG_SIZE) {
Expand Down Expand Up @@ -159,22 +159,6 @@ public function parse(string $data): Type
return Type::Unknown;
}

/**
* Not applicable — MongoDB does not use SQL
*/
public function classifySQL(string $query): Type
{
return Type::Unknown;
}

/**
* Not applicable — MongoDB does not use SQL
*/
public function extractKeyword(string $query): string
{
return '';
}

/**
* Extract the first key name from a BSON document
*
Expand Down
4 changes: 2 additions & 2 deletions src/Query/Parser/MySQL.php → src/Query/Classifier/MySQL.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Utopia\Query\Parser;
namespace Utopia\Query\Classifier;

use Utopia\Query\Type;

Expand Down Expand Up @@ -37,7 +37,7 @@ class MySQL extends SQL

private const COM_STMT_RESET = 0x1A;

public function parse(string $data): Type
public function classify(string $data): Type
{
$len = \strlen($data);
if ($len < 5) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Utopia\Query\Parser;
namespace Utopia\Query\Classifier;

use Utopia\Query\Type;

Expand All @@ -22,7 +22,7 @@
*/
class PostgreSQL extends SQL
{
public function parse(string $data): Type
public function classify(string $data): Type
{
$len = \strlen($data);
if ($len < 6) {
Expand Down
6 changes: 3 additions & 3 deletions src/Query/Parser/SQL.php → src/Query/Classifier/SQL.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

namespace Utopia\Query\Parser;
namespace Utopia\Query\Classifier;

use Utopia\Query\Parser;
use Utopia\Query\Classifier;
use Utopia\Query\Type;

/**
Expand All @@ -14,7 +14,7 @@
* Performance: Uses byte-level checks and simple string operations (no regex).
* Designed to run on every packet with sub-microsecond overhead.
*/
abstract class SQL implements Parser
abstract class SQL implements Classifier
{
/**
* Read keywords lookup (uppercase)
Expand Down
20 changes: 0 additions & 20 deletions src/Query/Parser.php

This file was deleted.

Loading
Loading