Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
202 changes: 202 additions & 0 deletions .claude/commands/add-operator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# add-operator

Add new MQL operator(s) or stage(s) to both `mql-specifications` and the PHP library builders.
This skill covers any definition change — triggered by a Jira ticket, a spec update, or manual need.

## Usage

`/add-operator [description of what to add]`

## Workflow

### 1. Research the operator spec

**From the MongoDB server source** (most reliable):

```bash
# IDL definition for a stage
gh api "repos/mongodb/mongo/contents/src/mongo/db/pipeline/document_source_<name>.idl" \
--jq '.content' | base64 -d

# For expressions: search expression.cpp and expression.h
gh api "search/code?q=<name>+repo:mongodb/mongo+path:src/mongo/db/pipeline/expression" \
--jq '.items[].path'

# Feature flag version
gh api "repos/mongodb/mongo/contents/src/mongo/db/query/query_feature_flags.idl" \
--jq '.content' | base64 -d | grep -A 8 "featureFlag<Name>"
```

Use existing similar definitions as models:
- Stage with object args → `generator/mql-specifications/definitions/stage/scoreFusion.yaml`
- Single-arg expression → `generator/mql-specifications/definitions/expression/sin.yaml`
- Single-arg expression → `generator/mql-specifications/definitions/expression/exp.yaml`

### 2. Create the mql-specifications branch

```bash
cd generator/mql-specifications
git checkout main
git pull upstream main
git checkout -b <short-branch-name>
```

### 3. Create the YAML definition file(s)

Create `generator/mql-specifications/definitions/<category>/<name>.yaml`.

**Template:**
```yaml
# $schema: ../../schemas/operator.json
name: $operatorName
link: https://www.mongodb.com/docs/manual/reference/operator/aggregation/<operatorName>/
minVersion: '8.1'
type:
- stage # see type reference below
encode: object # object | single | array
description: |
One-sentence description.
arguments:
- name: argName
type:
- object
description: |
Description.
- name: optionalArg
optional: true
type:
- bool
default: false
description: Description.
tests:
- name: Example
link: https://www.mongodb.com/docs/manual/reference/operator/aggregation/<name>/#examples
pipeline:
- $<name>:
# IMPORTANT: copy the example pipeline verbatim from the official MongoDB docs.
# Do not invent or adapt — use the exact operators, field names, and values shown.
```

**Type reference — operator type (what the operator is):**
- `stage` — regular aggregation stage
- `inputStage` — must be first stage in pipeline (`$search`, `$vectorSearch`, `$changeStream`)
- `outputStage` — must be last stage (`$out`, `$merge`)
- `updateStage` — usable in update pipelines
- `resolvesToDouble`, `resolvesToDecimal`, `resolvesToNumber`, `resolvesToBool`,
`resolvesToString`, `resolvesToDate`, `resolvesToInt`, `resolvesToLong`, `resolvesToAny`

**Type reference — argument types:**
- `object`, `bool`, `int`, `string`, `array`
- `query`, `pipeline`, `expression`
- `resolvesToNumber`, `resolvesToString`, `resolvesToBool`, `resolvesToAny`

**`encode` values:**
- `object` — operator has named arguments (`{ $op: { arg1: ..., arg2: ... } }`)
- `single` — operator takes one positional argument (`{ $op: <expr> }`)
- `array` — operator takes an array of arguments

### 4. Format and validate YAML

```bash
# Run from generator/mql-specifications/
yamlfix definitions/<category>/<name>.yaml
yamlfix --check definitions/<category>/<name>.yaml # must output 0 left unchanged

cd scripts/schema-validator
pnpm install
pnpm run validate # must validate all files with no errors
```

### 5. Commit and push to fork, create mql-specifications PR

```bash
cd generator/mql-specifications
git add definitions/<category>/<name>.yaml
git commit -m "Add $<name> <category> definition"
git push origin <branch-name>

gh pr create \
--repo mongodb/mql-specifications \
--head GromNaN:<branch-name> \
--base main \
--title 'Add $<name> <category>' \
--body "..."
```

### 6. Run the PHP code generator

The generator reads from the submodule working directory, so the new YAML files are
available immediately (no need to wait for the mql-specifications PR to be merged).

```bash
cd /Users/jerome/Develop/mongo-php-library/generator
./generate
```

**Generated files (do NOT edit manually — they are overwritten on each run):**
- `src/Builder/<Category>/<Name>Stage.php` (or `Operator.php`) — value object class
- `src/Builder/<Category>/FactoryTrait.php` — factory method added
- `src/Builder/<Category>/FluentFactoryTrait.php` — fluent method added
- `tests/Builder/<Category>/Pipelines.php` — expected JSON fixtures (auto-generated)
- `tests/Builder/<Category>/<Name>Test.php` — test class (regenerated, no AUTO-GENERATED header)

### 7. Review and complete the generated tests

The generator converts the YAML `tests` section into PHP. Review the generated test file:

```bash
cat tests/Builder/<Category>/<Name>Test.php
```

Check that:
- Builder API calls are idiomatic (use `Stage::`, `Expression::`, `Query::` factory methods)
- Complex nested structures use the proper PHP types (`Pipeline`, typed builders) not raw arrays
- The `@todo` comments left by the generator are resolved

To improve a test, **edit the YAML `tests` section** and re-run `./generate` — the test PHP
is always regenerated from YAML. Only fix things that the generator cannot express.

Run the tests to confirm they pass:

```bash
cd /Users/jerome/Develop/mongo-php-library
composer test -- --filter <NameTest>
```

### 8. Fix code style and static analysis

```bash
composer fix:cs
composer check:cs
composer check:psalm
```

### 9. Create the mongo-php-library PR

Commit and push simultaneously with (or right after) the mql-specifications PR:

```bash
git add generator/mql-specifications src/Builder/ tests/Builder/
git commit -m "Add $<name> support"
gh pr create --repo mongodb/mongo-php-library --base v2.x \
--title 'Add $<name> <category>' \
--body "..."
```

PR body should reference:
- The mql-specifications PR (mongodb/mql-specifications#XX)
- The Jira ticket if applicable (PHPLIB-XXXX / DRIVERS-XXXX)

## Notes

- The submodule `generator/mql-specifications` has two remotes:
- `origin` → `GromNaN/mongodb-mql-specifications` (your fork — push here)
- `upstream` → `mongodb/mql-specifications` (source of truth — pull from here)
- After the mql-specifications PR is merged, update the submodule pointer:
```bash
cd generator/mql-specifications && git pull upstream main
cd .. && git add generator/mql-specifications
git commit -m "Update mql-specifications submodule"
```
- The `tests/Builder/<Category>/<Name>Test.php` has no `AUTO-GENERATED` header but IS
regenerated on every `./generate` run — always improve via the YAML, not the PHP directly.
75 changes: 75 additions & 0 deletions .claude/commands/review-specs-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# review-specs-update

Review a Dependabot PR that bumps the `tests/specifications` submodule.

## Usage

`/review-specs-update <PR number>`

## Workflow

### 1. Fetch PR details

```bash
gh pr view <PR> --repo mongodb/mongo-php-library --json title,body,commits,files
```

Extract the list of specifications commits from the PR body (the `<ul>` under "Commits"). Each entry contains a commit SHA and a DRIVERS-XXXX ticket reference.

### 2. List changed spec files

```bash
gh api repos/mongodb/specifications/compare/<old_sha>...<new_sha> --jq '.files[].filename'
```

The old and new SHAs are in the PR body (`from \`XXXXXXX\` to \`XXXXXXX\``).

### 3. Check CI status

```bash
gh pr checks <PR> --repo mongodb/mongo-php-library
```

### 4. For each DRIVERS ticket: find the PHPLIB/PHPC split-to ticket

Use WebFetch on `https://jira.mongodb.org/browse/DRIVERS-XXXX` — Jira is public.

Look for the **"Split to"** linked issues (PHPLIB-XXXX or PHPC-XXXX).

If WebFetch fails (login redirect), try a JQL search:

`https://jira.mongodb.org/issues/?jql=project%20in%20(PHPLIB%2C%20PHPC)%20AND%20text%20~%20%22DRIVERS-XXXX%22`

### 5. Identify if code/test changes are needed

Cross-reference the changed spec files with existing PHP tests:

- `source/transactions-convenient-api/` → `tests/UnifiedSpecTests/` + `withTransaction` tests
- `source/client-backpressure/` → search for backpressure in `tests/`
- `source/change-streams/` → `tests/UnifiedSpecTests/` change-stream tests
- `source/retryable-reads/` or `source/retryable-writes/` → corresponding spec tests

Check if new YAML/JSON test files in the specs have corresponding entries in PHP test runners or are automatically picked up.

### 6. Conclusion

**If CI passes and no code changes needed:**
1. Approve and squash-merge the PR:
```bash
gh pr review <PR> --repo mongodb/mongo-php-library --approve
gh pr merge <PR> --repo mongodb/mongo-php-library --squash
```
2. For each PHPLIB/PHPC ticket found, note that a comment should be added:
> "Spec tests updated in mongodb/mongo-php-library#PR"

**If CI fails:** Identify the failing test and add it to `$incompleteList` in the relevant test class to skip it. The PHPLIB/PHPC ticket already exists — just note it needs implementation work.

## Output format

Present a summary table:

| Commit | DRIVERS ticket | PHPLIB/PHPC ticket | Changed specs | Action needed |
|---|---|---|---|---|
| `abc1234` | DRIVERS-XXXX | PHPLIB-YYYY | `source/foo/` | Comment on PHPLIB-YYYY |

Then state clearly: **CI passes / fails** and what to do next.
2 changes: 1 addition & 1 deletion generator/src/AbstractGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class AbstractGenerator
public function __construct(
private string $rootDir,
) {
$this->printer = new ClassPrinter();
$this->printer = new TypeClassPrinter();
}

/**
Expand Down
11 changes: 11 additions & 0 deletions generator/src/Definition/ArgumentDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use InvalidArgumentException;

use function array_is_list;
use function array_map;
use function assert;
use function get_debug_type;
use function get_object_vars;
use function is_array;
use function is_object;
use function is_string;
Expand All @@ -22,6 +24,9 @@ final class ArgumentDefinition
public VariadicType|null $variadic;
public int|null $variadicMin;

/** @var list<self> Sub-arguments for object-typed arguments. */
public array $arguments;

public function __construct(
public string $name,
/** @var list<string> */
Expand All @@ -33,6 +38,7 @@ public function __construct(
public mixed $default = null,
public bool $mergeObject = false,
public string|null $minVersion = null,
array $arguments = [],
mixed ...$ignoredOtherArgs,
) {
assert($this->optional === false || $this->default === null, 'Optional arguments cannot have a default value');
Expand Down Expand Up @@ -64,5 +70,10 @@ public function __construct(
if ($this->minVersion && version_compare($this->minVersion, '4.4', '>=')) {
$this->description .= sprintf("\nNew in MongoDB %s\n", $this->minVersion);
}

$this->arguments = array_map(
static fn (object $arg): self => new self(...get_object_vars($arg)),
$arguments,
);
}
}
Loading
Loading