Conversation
…ith static helpers
… raw, and convenience methods
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Fix compileIn/compileNotIn to return `1 = 0` / `1 = 1` for empty arrays instead of invalid `IN ()` / `NOT IN ()` - Fix NOT MATCH() syntax: wrap in parentheses for valid MySQL (`NOT (MATCH(...) AGAINST(?))`) - Fix toRawSql SQL injection: escape single quotes in string bindings - Fix toRawSql corruption: use substr_replace instead of preg_replace to avoid `?` and `$` in values corrupting output - Fix page(0, n) producing negative offset: clamp to max(0, ...) - Guard compileLogical/compileExists/compileNotExists against empty arrays producing bare `()` - Simplify null tracking in compileIn/compileNotIn with boolean flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix dotted identifier wrapping (users.id → `users`.`id`)
- Escape wrap character in identifiers to prevent SQL injection
- Wrap _cursor column in identifier quotes
- Return 1=1 for empty raw SQL to prevent invalid WHERE clauses
- Treat COUNT('') as COUNT(*)
- Only emit OFFSET when LIMIT is present
- Escape LIKE metacharacters (% and _) in user input
- Validate JOIN operator against allowlist
- Fix condition provider binding order mismatch with cursor - Wrap UNION queries in parentheses for correct precedence - Validate ClickHouse SAMPLE fraction range (0,1) - Use explicit map for aggregate SQL function names - Escape backslashes in LIKE pattern values
… built-in implementations
… introduce value objects
…into Builder namespace
Previously readString() and readQuotedIdentifier() silently returned a token when EOF was reached before the closing quote. This produced invalid tokens that broke downstream parsing. Now both raise ValidationException on EOF before the terminator, so malformed input is rejected at tokenization time.
Emit `ORDER BY tuple()` when no primary keys are declared so the generated MergeTree DDL remains valid, and throw a ValidationException when ALTER TABLE is called with no alterations instead of producing truncated SQL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sitors fire Walker::walk() documents — and FilterInjector relies on — the contract that visitSelect() is called on every Select node, including subqueries. But subqueries embedded in expressions (EXISTS, scalar Subquery, IN (SELECT ...)) were being traversed via walkStatement() directly, which skips visitSelect(). Visitors hooking on visitSelect() silently missed those nested Selects. Route Exists, Subquery and In-with-Select descents through walk() so the visitor fires on the nested Select, matching the behaviour for SubquerySource and CTEs. Add regression tests covering all three expression contexts with FilterInjector to verify the condition is applied to both outer and nested Selects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Harden the MongoDB wire-protocol parser against malformed BSON input. Every length-prefixed read (nested documents, regex cstrings, DBPointer ObjectId, all fixed-width skips) now validates the resulting position against the document limit before advancing; the outer document length is also rejected when negative or beyond the packet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds round-trip coverage against the mongo:7 container for MongoDB-specific update operators, pipeline stages, and Schema collection/index management. Also wires options through executeUpdateMany and accepts a top-level validator on createCollection in the test client so the new tests can run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SQLite's compound-SELECT parser rejects the parenthesised form
`(SELECT ...) UNION (SELECT ...)` with `near "(": syntax error`,
while MySQL/PostgreSQL/ClickHouse all tolerate it. Introduce a
`wrapUnionMember()` dialect hook on the base Builder so SQLite
can opt out while every other dialect keeps the existing shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sert Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ount Add fluent API for CHECK constraints (table-level on Blueprint, column-level on Column), generated columns (stored/virtual), and MySQL PARTITION BY HASH partition count. ClickHouse throws UnsupportedException for CHECK and GENERATED since the engine lacks those primitives. PostgreSQL emits STORED only and rejects virtual() at compile time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add ColumnType::Serial, BigSerial, SmallSerial. Engine compilers: PostgreSQL emits SERIAL/BIGSERIAL/SMALLSERIAL (real sequence-backed). MySQL/MariaDB map to INT/BIGINT/SMALLINT. SQLite maps to INTEGER (with AUTOINCREMENT when paired with primary()). MongoDB maps to int. ClickHouse throws UnsupportedException. User-defined type references (PG enum etc.) are guarded in non-PostgreSQL engines' compileColumnType with UnsupportedException. Migrates PG integration tests off rawColumn() to the typed API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New Schema\ClickHouse\Engine enum covering MergeTree, ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree, ReplicatedMergeTree, Memory, Log, TinyLog, StripeLog. Blueprint::engine(Engine, ...$args) routes into the ClickHouse compiler; required args validated (CollapsingMergeTree needs sign column, ReplicatedMergeTree needs zookeeper_path + replica_name). Non-MergeTree engines skip the ORDER BY tuple() fallback. Blueprint::ttl() emits table-level TTL; Column::ttl() emits column TTL. Non-ClickHouse engines throw UnsupportedException for both. Migrates ClickHouse integration tests off raw DDL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed check/generatedAs Use the typed Blueprint::check() and Column::generatedAs()->stored() APIs added in 4d7a397 instead of rawColumn() string defs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Typed API for composite PRIMARY KEY across two or more columns, emitted as a table-level constraint in MySQL/MariaDB/PostgreSQL/SQLite and folded into ORDER BY for ClickHouse. MongoDB throws UnsupportedException since documents use _id implicitly. Validates column names against [a-zA-Z_][a-zA-Z0-9_]* and rejects combining column-level primary() with Blueprint::primary(). Migrates the PostgreSQL partitioned-table integration test off the three rawColumn() calls to typed integer()/timestamp()/primary(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+ PostgreSQL
Adds a dialect-neutral Sequences interface so callers can emit typed
NEXTVAL/CURRVAL select expressions instead of selectRaw() strings.
- MariaDB compiles nextVal() to NEXTVAL(`seq`) and currVal() to LASTVAL(`seq`)
(MariaDB does not expose a CURRVAL()).
- PostgreSQL compiles nextval('seq') / currval('seq') as string-literal
sequence references.
Sequence names are validated against [a-zA-Z_][a-zA-Z0-9_]* before quoting
to prevent injection through the sequence name.
Migrates testSequences in the MariaDB integration suite off selectRaw().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace havingRaw('COUNT(*) > ?', [1]) with the typed
having([Query::greaterThan('order_count', 1)]) form in the PostgreSQL and
SQLite integration tests. The HAVING compiler already resolves SELECT-list
aggregate aliases back to their underlying expressions, so the emitted SQL
is identical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Blueprint was a Laravel-ism. Doctrine DBAL and Phinx use Table, which is also the conventional PHP variable name for the schema DSL object. Rename the class + file + all ~330 callsites. No behavior change; mechanical rename. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Query Plan overloaded "execution plan" terminology — rename to Statement, which better describes the compiled query + bindings + executor returned by build(). Coexists with AST\Statement\Select via distinct namespaces. GroupedQueries was misleading — the class is a parsed representation of filter/select/aggregate/groupBy/having/join/union clauses, nothing is SQL-grouped. Rename to ParsedQuery. No behavior change; mechanical rename across ~50 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The INSERT subquery used bare integer literals which ClickHouse infers as UInt8, producing maxState(UInt8) aggregate state. The destination column is AggregateFunction(max, UInt32), and ClickHouse's strict aggregate-function type system rejects implicit UInt8 -> UInt32 conversion, raising CANNOT_CONVERT_TYPE at insert time. Cast both the key and value columns via toUInt32 so the source aggregate state matches the destination column type. Keeps the test's intent (verify AggregatingMergeTree compiles and stores merge-safe aggregates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ClickHouse does not expose bare STDDEV or VARIANCE aliases, so the shared emission of STDDEV(col) / VARIANCE(col) errored at runtime against a real server. Override stddev() and variance() on the ClickHouse builder to emit stddevPop / varPop (ISO SQL population variants). The existing stddevPop / stddevSamp / varPop / varSamp methods keep emitting their named functions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the Hints trait validator to accept backticks and dots so real MySQL index hints such as INDEX(`table` `idx`) and FORCE INDEX (`idx`) pass validation. Adds regression tests covering accepted forms and rejected injection attempts (semicolons, null bytes, newlines, block comments, single quotes). ClickHouse override remains untouched since its SETTINGS-style hints don't use backticks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The test selected `l.id` and `r.id` without aliases, so PDO::FETCH_ASSOC collapsed both columns onto the single key `id`, keeping only the right side. The left-only row's `id` became null and the matched row's `id` became 2, so asserting the left-only id (1) was present failed. Alias the columns to `left_id`/`right_id` so both sides are inspectable, and assert the full expected set on each side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…river The arrayFilter() trait was wrapping the user-supplied condition document under the identifier key, producing `[['elem' => ['elem.grade' => ...]]]` instead of the MongoDB-expected filter document `[['elem.grade' => ...]]`. As a result the positional `$[elem]` update path silently matched no elements and left the array untouched. Store the condition as-is so the arrayFilters option forwarded to updateMany matches MongoDB's spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PDOException::getCode() returns the SQLSTATE ('HY000' for a CHECK
violation), not the MySQL driver-specific error number. The
driver-specific 3819 lives in errorInfo[1]. The old assertion could
never pass because it searched for '3819' inside 'HY000'.
The CHECK constraint DDL itself was already emitted correctly by
the schema compiler (CONSTRAINT <name> CHECK (<expr>) inside the
column list of CREATE TABLE), and MySQL 8.4 does enforce it — the
exception was being thrown, just with an assertion targeting the
wrong field on PDOException.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR introduces a large query builder subsystem (
Confidence Score: 4/5Safe to merge with the FETCH NEXT parsing gap addressed; remaining findings are style/hardening. One P1 defect (FETCH NEXT parse failure) means valid SQL will throw at runtime. The remaining two findings are P2 and do not block merge on their own, but the P1 warrants a fix before shipping. src/Query/AST/Parser.php (FETCH clause), src/Query/AST/Visitor/ColumnValidator.php (Star handling)
|
| Filename | Overview |
|---|---|
| src/Query/AST/Parser.php | New SQL AST parser (1211 lines); missing FETCH NEXT / singular ROW support, and a dead guard condition in CTE column list parsing |
| src/Query/AST/Visitor/ColumnValidator.php | New visitor for column allowlist enforcement; Star/wildcard expressions bypass the check, creating a potential security gap |
| src/Query/Builder.php | Large new abstract Builder (2806 lines); bindings reset correctly on each build()/update()/delete() call, clone logic looks thorough |
| src/Query/AST/Serializer.php | New AST serializer with correct precedence handling, safe literal escaping, and proper parenthesization for non-commutative operators |
| src/Query/AST/Walker.php | New AST walker with bottom-up traversal; covers all expression types including Conditional/CaseWhen, subqueries, and CTEs |
| src/Query/AST/Visitor/FilterInjector.php | New visitor that injects a WHERE condition; correctly documents Walker vs direct-call semantics for subquery scope |
| src/Query/QuotesIdentifiers.php | New identifier-quoting trait; correctly handles dotted identifiers and wildcard segments |
Reviews (1): Last reviewed commit: "fix(test): assert MySQL driver error cod..." | Re-trigger Greptile
| } | ||
|
|
||
| return new Select( | ||
| columns: $columns, | ||
| from: $from, | ||
| joins: $joins, |
There was a problem hiding this comment.
FETCH NEXT and singular ROW not handled
The parser only accepts FETCH FIRST n ROWS ONLY. The SQL standard (and PostgreSQL, DB2, etc.) also allows FETCH NEXT n ROWS ONLY and the singular form FETCH FIRST/NEXT n ROW ONLY. Any query using either alternative will throw a parse exception at consumeKeyword('FIRST').
// Suggested fix
if ($this->matchKeyword('FETCH')) {
$this->advance();
if (!$this->matchKeyword('FIRST') && !$this->matchKeyword('NEXT')) {
throw new Exception("Expected FIRST or NEXT after FETCH at position {$this->current()->position}");
}
$this->advance(); // consume FIRST or NEXT
$limit = $this->parseExpression();
if (!$this->matchKeyword('ROW') && !$this->matchKeyword('ROWS')) {
throw new Exception("Expected ROW or ROWS at position {$this->current()->position}");
}
$this->advance(); // consume ROW or ROWS
$this->expectIdentifierValue('ONLY');
}| $this->expect(TokenType::RightParen); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Dead condition in CTE column list guard
When $this->current()->type === TokenType::LeftParen, matchKeyword('AS') always returns false (the current token is a LeftParen, not a Keyword). So !$this->matchKeyword('AS') is unconditionally true here — the conjunction is redundant and misleadingly implies an AS-check that never fires. The actual disambiguation is handled by peekIsColumnList().
| if ($this->current()->type === TokenType::LeftParen) { |
| public function visitExpression(Expression $expression): Expression | ||
| { | ||
| if ($expression instanceof Column) { | ||
| if (!in_array($expression->name, $this->allowedColumns, true)) { | ||
| throw new Exception("Column '{$expression->name}' is not in the allowed list"); | ||
| } | ||
| } | ||
| return $expression; | ||
| } |
There was a problem hiding this comment.
Star expressions bypass the column allowlist
visitExpression only inspects Column nodes. A Star (wildcard * or table.*) expression passes through unchecked. If ColumnValidator is used as a security gate to prevent access to specific columns, SELECT * or SELECT t.* will silently bypass the whitelist, potentially exposing columns the caller intended to restrict.
Consider explicitly rejecting (or explicitly allowing) Star nodes.
No description provided.