Repository navigation
feat: add Query.Builder, aggregates, joins, and nested ON - #1817
Conversation
|
|
@greptileai review |
1 similar comment
|
@greptileai review |
Query helpers now cover the query-lib methods Appwrite allows (aggregates, groupBy, having, distinct, joins, extra spatial). Query.builder()/build() collects filters as a flat string list, and Client.normalize unwraps the common extra-array wrap of a builder or build()/page() result so list and POST /query calls stay valid.
`any` does not exist on Go 1.13, which still compiles the SDK in CI. Realtime subscription updates now go through Query.normalize instead of iterating QueryInput, and Client.subscribe no longer writes a queries set that is not on the Realtime connection type.
Query.normalize was only flattening a builder or nested build()/page() array before send. Call sites already do that on the queries param, so it does not belong on the public Query helper surface.
Keep the simple join(table, left, right, op, alias) triple. Array overloads encode Query.on() plus filter queries into nested join values so extra ON predicates stay on the JOIN.
Add e2e prints and unit tests for aggregates, joins, ON, spatial, and page so each SDK exercises every new query type.
Clients now map every param through a type-based flatten so builders and extra nested lists unwrap without special-casing the queries key. Document the new public Query helpers.
Live specs encode closed enums as type:string plus oneOf of single-value string schemas. Parsing that as an object made path params Any, JSON keys the enum title, and Ruby docs crash on non-JSON examples. Keep model JSON keys as the property name, type signatures from the collapsed enum, and flatten Dart request maps as Map<String, dynamic>.
420fd0f to
844e3e1
Compare
Collapsed oneOf string enums with no title produced empty type names (`val status: ,`, `resourceType: ;`) and invalid PHP properties.
Move QueryBuilder into Query/Builder files and nested types where the language allows it. Alias QueryBuilder at import for PHP and the TypeScript public export.
Dart part-of is resolved relative to lib/query, so the builder library looked for packageName.dart in the wrong directory, and a top-level Builder class collides with Flutter's widget. Go treats query.Builder() as a type conversion. PHP mock payloads and from() treated array-of-enum fields as a single string, which is how live-spec OAuth prompt examples are encoded. CLI Validation now compiles against a Go SDK generated from the same spec instead of the last published module.
A replace in go.mod is the local pin; Validation no longer needs an env override or go mod edit. example.php writes examples/go from the same spec so ../go matches the CLI module path.
Request flattening is a transport concern. Keep Query as the helper surface and run flatten from Client in Kotlin, Android, Swift, and Apple.
Keep annotated enumerations from main and the query builder work. CLI go.mod still replace-pins the sibling generated Go SDK at v7.2.0-rc.3.
There was a problem hiding this comment.
Pull request overview
Adds cross-language query aggregation, grouping, joins, spatial operators, fluent builders, request flattening, and enum-handling improvements to generated SDKs.
Changes:
- Expands Query APIs and adds fluent builders across supported SDK languages.
- Normalizes nested query inputs in clients and adds broad E2E/unit coverage.
- Improves closed
oneOfenum detection and scalar array-enum hydration.
Reviewed changes
Copilot reviewed 110 out of 110 changed files in this pull request and generated 29 comments.
Show a summary per file
| File(s) | Description |
|---|---|
tests/resources/spec-openapi3.json |
Adds closed oneOf enum fixtures. |
tests/e2e/Base.php |
Adds helper-output and enum hydration assertions. |
tests/e2e/languages/{web,node,react-native,deno}/* |
Exercises TypeScript query helpers and builders. |
tests/e2e/languages/{swift,apple}/Tests.swift |
Exercises Swift query helpers. |
tests/e2e/languages/{kotlin,android}/Tests.kt |
Exercises Kotlin query helpers. |
tests/e2e/languages/{go,go-v2}/tests.go |
Exercises Go query helpers. |
tests/e2e/languages/{dart,flutter}/tests.dart |
Exercises Dart query helpers. |
tests/e2e/languages/{dotnet,unity}/Tests.cs |
Exercises C# query helpers. |
tests/e2e/languages/{ruby,python,php}/* |
Exercises dynamic-language query helpers. |
templates/web/src/{query,client,index}.ts.twig |
Adds Query builder/input normalization and exports. |
templates/web/src/services/realtime.ts.twig |
Normalizes realtime query inputs. |
templates/node/src/{client,index}.ts.twig |
Adds Node request flattening and exports. |
templates/node/test/query.test.js.twig |
Tests Node query behavior. |
templates/react-native/src/{query,client,index}.ts.twig |
Adds React Native query building and flattening. |
templates/deno/{mod.ts,src/query.ts,src/client.ts}.twig |
Adds Deno query building and flattening. |
templates/deno/test/query.test.ts.twig |
Tests Deno query behavior. |
templates/swift/Sources/Query*.twig |
Adds Swift helpers and nested builder. |
templates/swift/Sources/Client.swift.twig |
Flattens Swift request parameters. |
templates/swift/{Tests/Tests.swift,Package.swift}.twig |
Adds tests and conditional package targets. |
templates/apple/{Sources/Client.swift,Package.swift}.twig |
Applies inherited Swift client/package updates. |
templates/kotlin/src/main/kotlin/io/appwrite/{Query,Client}.kt.twig |
Adds Kotlin query APIs and flattening. |
templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig |
Adds Kotlin Query tests. |
templates/android/library/src/main/java/io/package/{Query,Client}.kt.twig |
Adds Android query APIs and flattening. |
templates/go/query*.twig |
Adds Go helpers, builder, and tests. |
templates/go/client.go.twig |
Flattens Go request values. |
templates/rust/src/query/{mod,builder}.rs.twig |
Adds Rust Query APIs and builder. |
templates/rust/src/client.rs.twig |
Normalizes Rust request parameters. |
templates/rust/tests/tests.rs |
Exercises Rust query helpers. |
templates/dotnet/Package/Query*.twig |
Adds .NET helpers and nested builder. |
templates/dotnet/Package/Client.cs.twig |
Flattens .NET request parameters. |
templates/php/src/{Query,Client}.php.twig |
Adds PHP helpers, builder integration, and flattening. |
templates/php/src/Query/Builder.php.twig |
Implements the PHP fluent builder. |
templates/php/src/Models/{Model,RequestModel}.php.twig |
Wraps scalar array-enum values during hydration. |
templates/php/tests/QueryTest.php.twig |
Tests PHP Query behavior. |
templates/python/package/{query,client}.py.twig |
Adds Python builder APIs and flattening. |
templates/python/test/test_query.py.twig |
Tests Python Query behavior. |
templates/ruby/lib/container/{query,client}.rb.twig |
Adds Ruby helpers and request flattening. |
templates/ruby/lib/container/query/builder.rb.twig |
Implements the Ruby fluent builder. |
templates/dart/lib/{query,package}.dart.twig |
Adds Dart Query APIs and builder registration. |
templates/dart/lib/query/builder.dart.twig |
Implements QueryBuilder. |
templates/dart/lib/src/client_mixin.dart.twig |
Flattens Dart request parameters. |
templates/dart/lib/src/models/{model,request_model}.dart.twig |
Handles scalar array-enums and enum serialization. |
templates/dart/test/{query_test,src/models/model_test}.dart.twig |
Extends Dart Query/model tests. |
templates/flutter/lib/{package,src/client_mixin}.dart.twig |
Registers builders and flattens Flutter parameters. |
templates/skills/*.md.twig |
Documents query aggregation, joins, and builders. |
templates/cli/internal/typegen/templates/{types,databases}.ts.hbs |
Extends typed CLI Query helpers. |
templates/cli/internal/generator/typescript.go |
Reuses the generated QueryBuilder type. |
templates/cli/internal/generator/testdata/* |
Updates expected TypeScript output. |
templates/cli/go.mod.twig |
Redirects CLI builds to a local generated Go SDK. |
src/SDK/SDK.php |
Improves enum-name fallback resolution. |
src/SDK/Language.php |
Resolves untitled enum names from parent properties. |
src/SDK/Language/{Swift,Apple}.php |
Registers Swift builder output. |
src/SDK/Language/{Kotlin,Android}.php |
Registers Query tests. |
src/SDK/Language/{Dart,Flutter}.php |
Registers Dart builder output. |
src/SDK/Language/{Go,Rust,PHP,Ruby,DotNet}.php |
Registers new builder/module templates. |
example.php |
Generates Go alongside CLI and updates the Go SDK version. |
Suppressed comments (2)
templates/cli/internal/typegen/templates/types.ts.hbs:76
- These fixed scalar signatures cannot express the new nested JOIN ON form, and the facade exposes no
onhelper. Addonplus overloads/unions for(table, on)and(table, alias, on)so the typed callback can construct the nested wire shape supported byQuery.
join: (table: string, left: string, right: string, operator?: string, alias?: string) => string;
leftJoin: (table: string, left: string, right: string, operator?: string, alias?: string) => string;
rightJoin: (table: string, left: string, right: string, operator?: string, alias?: string) => string;
fullOuterJoin: (table: string, left: string, right: string, operator?: string, alias?: string) => string;
templates/cli/internal/typegen/templates/databases.ts.hbs:44
- The facade delegates only the scalar join form and does not expose
Query.on, so typed CLI callbacks cannot use the nested JOIN ON feature added by this PR. Mirror the nested overloads andonhelper exposed byQuery.
join: (table, left, right, operator = '=', alias = '') => Query.join(table, left, right, operator, alias),
leftJoin: (table, left, right, operator = '=', alias = '') => Query.leftJoin(table, left, right, operator, alias),
rightJoin: (table, left, right, operator = '=', alias = '') => Query.rightJoin(table, left, right, operator, alias),
fullOuterJoin: (table, left, right, operator = '=', alias = '') => Query.fullOuterJoin(table, left, right, operator, alias),
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Rust was treating every nested array as query input, Go compared uncomparable slices, and Dart/Flutter left wrapped builders nested. Skills now show build() only; typed CLI query helpers include the new aggregates. Regenerating Rust deletes leftover src/query.rs.
|
Went through Copilot's 29 threads. Fixed
Left as-is
|
Language e2e tests now print flatten-builder/geometry/list so a wrapped builder that stays nested or a numeric array that gets spread fails CI. Query flatten is callable from those tests; PHP form flatten is flattenForm so it no longer collides with Client::flatten.
Typed QueryBuilder gained stddev/variance/bitwise; baseline fixtures still expected groupBy at that offset and failed TestGenerateMatchesBaseline.
|
Pushed two commits on top of the flatten-bugfix head:
CLIGo126 / Validation |
|
@greptileai review |
Flutter query_test constructing Client() hits path_provider before the widget binding exists. Apple flatten returns [Any], so as? [String] failed even when the query strings were correct.
|
@greptileai review |
Linux Swift does not cast [Query.Builder] or [String] to [Any], so flatten left wrapped builders nested. Walk those typed arrays (and Mirror as a fallback) so builder lists unwrap on Apple and Swift.
Validation php (server) runs Pint before Rector; fn($item) fails function_declaration.
Both sides appended to the same two E2E language fixtures. Only the
destructuring import at the top of each file actually conflicted; git
merged the bodies cleanly.
tests/e2e/languages/node/test.js
tests/e2e/languages/web/node.js
Each import now names main's AppwriteException and this branch's
flattenParam. Neither side was dropped: against either parent the merged
fixtures delete nothing except the one import line that parent rewrote.
The fixtures are programs whose stdout is compared line by line against
tests/e2e/Base.php, so ordering had to follow what the generated SDK
actually emits rather than the order git interleaved the hunks. Base.php
itself merged as a superset of both sides, and the merged $expectedOutput
of each owning test now reads:
Node18/20/26 ... GENERAL, PATH_VALIDATION, PATH_PARAM, uploads,
DOWNLOAD, ENUM, MODEL, OPTIONAL_PARAM, EXCEPTION, OAUTH,
QUERY_HELPER, QUERY_FLATTEN, PERMISSION, ID, OPERATOR
WebNode/Chromium ... GENERAL, PATH_VALIDATION, PATH_PARAM, ENUM,
MODEL, OPTIONAL_PARAM, UNION, EXCEPTION, QUERY_HELPER,
QUERY_FLATTEN, PERMISSION, ID, CHANNEL, OPERATOR
which places main's path-validation block between general.redirect() and
getPath(), main's getOptional() pair between the request-model tests and
the error cases, this branch's aggregate, join, nested-ON, spatial and
pagination helpers at the end of the existing query-helper run, and the
three flattenParam checks after them as QUERY_FLATTEN. No expectation
needed moving by hand.
Proven against real generated output, not read by eye:
Node18Test OK (1 test, 194 assertions)
Node20Test OK (1 test, 194 assertions)
Node26Test OK (1 test, 194 assertions)
WebNodeTest OK (1 test, 222 assertions)
WebChromiumTest OK (1 test, 233 assertions)
Generation 85 tests, 1130 assertions, 4 skipped
phpcs, rector exit 0
Seen red first: restoring either parent's import line on its own fails
Node20Test, so the merged line carries weight from both sides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The new query contract printed each helper's rendering in process and compared it against a string, and reported flatten behaviour as a pass/fail label the harness computed for itself. Nothing crossed the client, so a harmless change of representation broke the test while a real regression on the transport path (a dropped array index, a mangled parameter, a builder that never got spread) still passed it. The helpers now travel as the queries parameter of a real listRows call, and the assertion reads them back from the echo the mock server returns, so what is pinned is the request the server received. Query.page and Query.builder feed that list instead of being indexed, and the .NET harness no longer requires Query.Flatten to hand back the same collection instance. Flatten keeps its coverage on the path a caller can reach: every request now carries its queries through the client's flatten before transport, and the languages whose service signature accepts a builder pass an unbuilt one so the server proves it was spread. The generated SDKs' own unit tests still assert the helper directly. Deno's nested-join helper referenced an undefined JSONbig, so the nested ON overload threw at runtime. The Deno suites are absent from the CI matrix, which is why only a run that reaches the server could see it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@greptileai review Addressed the Tests Mirror Query Internals finding ( What the assertions pinned before
What they assert nowEvery language harness builds those helpers into the Flatten keeps its coverage, on the path a caller can reach
Seen red, then green
One real defect fell out of this
Run locally: Node18, Node20, Node26, WebNode, WebChromium, Deno1303, PHP85, Python313, Ruby27, Ruby31, Go113, Go118, DotNet80, DotNet90, DartStable, FlutterStable, KotlinJava17, Swift61, Rust183, plus |
The Node, Deno and Python query tests compared each helper against a literal JSON string, so key order, whitespace and number formatting were pinned even though the server accepts any of them. The PHP, Go, Dart, Kotlin, Swift and Rust tests in this repository already decode the query and assert its fields; these three now do the same. Five Deno expectations were describing a shape the SDK has not emitted for a while: isNull and isNotNull carried an empty values array, select put its attributes under attribute, and both cursor helpers put their id there too. Nothing runs the Deno unit tests, so nothing said so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@greptileai review Addressed the Tests Mirror Serialization Internals finding. The three generated-SDK test files that compared a helper against a literal JSON string now decode the query and assert its fields, so key order, whitespace and number formatting are free to change while method, attribute and values stay pinned:
The finding also named PHP, Go, Dart, Kotlin, Swift and Rust. Those six already decode the query and assert its fields ( Converting Deno surfaced five expectations that had drifted from the SDK: Verified
|
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comments Outside DiffThese findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.
|
Unity overrides getFiles() instead of inheriting the .NET plan, so the shared dotnet/Package/Query.cs.twig it renders referenced Query.Builder with nothing emitting the partial that declares it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
configureSDK merged only exclude.services into the defaults, so the CLI's exclude.methods override replaced the whole exclude block and the CLI regained documentsDB and vectorsDB. Against the published sdk-for-go that went unnoticed; against the locally generated SDK pinned through go.mod's replace, those packages do not exist and the CLI fails to build. Merge every exclude kind, and generate the pinned SDK on the CLI's server platform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🟢 Tier S · Ready to merge
This pull request adds language-native Query helpers and fluent builders across the supported SDKs, including aggregate, grouping, spatial, join, and nested JOIN ON APIs. It updates client-side parameter flattening and serialization to preserve ordinary nested payloads while expanding Query values, and extends enum handling for live-spec shapes. Generated SDK tests, CLI type-generation fixtures, and language usage guidance are expanded to exercise the new surface. Latest changes: The newest commit switches the Go client test body reader to ioutil.ReadAll so the generated test remains compatible with the Go 1.13 test target.
📂 Walkthrough · 8
Reviewed the commits since |
go.mod replaced sdk-for-go with ../go for every non-test CLI, so a released CLI only built inside this repository's examples layout. The replace now comes from an sdkPath parameter that only example.php sets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prepareRequest flattened builders for GET and multipart but serialised the original params for JSON bodies, so a Query.Builder went out as an object instead of its query list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parsing nested queries back into Query relied on Gson reflection over fields R8 renames, so shrunk apps sent nested ON and HAVING queries without their method. Parse them into maps, as or/and/elemMatch do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generated QueryBuilder only offered the scalar join form, so typed callbacks could not express the nested ON conditions the SDKs accept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clients spliced any nested list of strings into its parent, assuming it was a built query list, so a JSON value such as [['a','b']] was sent as ['a','b']. Builders are now recognised by type and every other value is sent unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The findings that requested changes are resolved. See the summary comment for what is still open.
|
Merged
Follow-up fixes:
Behaviour change to review: a list is no longer spliced unless an item is a builder, so @greptileai review |
io.ReadAll only exists from Go 1.16, and the generated SDK still builds on 1.13. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why
Appwrite query-lib added aggregates, grouping, joins, extra spatial operators, and a POST
/querybody so largequeriesarrays are not capped by URL length. SDKs that already exposeQueryhelpers need those methods, a fluent collector, and a way to nest JOIN ON conditions without leaking SQL.What changed
count,sum,avg, …),groupBy/having/distinct, joins (join/leftJoin/rightJoin/fullOuterJoin/crossJoin), extra spatial (covers, …).Query.Builderwhere the language allows it (C#/Swift/Kotlin/Ruby/Python/TS namespace). PHP isAppwrite\Query\Builderimported asQueryBuilder. Go/Rust use packageBuilderwithNewBuilder()/query::Builder. Dart cannot nest classes and must not export a top-levelBuilder(Flutter widget clash), so the type isQueryBuilderinlib/query/builder.dart.Query.on(...)plus join overloads that take an alias, an ON query, and extra filters. The wire shape is[alias?, Query.on, filters…].Query.normalize. Flattening is structural (builder / nested query-string lists) and does not special-case thequeriesparameter name.type: stringplusoneOfof single-value schemas are treated as wire enums. Untitled oneOf members take their name from the parent property.from()and generated mocks wrap a scalar example for an array-of-enum field into a one-element list (OAuthpromptin the live spec is a string example on an array field).QUERY_HELPER_RESPONSEScovers each new helper in every SDK language test.Raw/union/json/naturalJoin stay unexposed — those are not on the Documents validator allow-list.
Vector helpers were already on
main; duplicate copies introduced during the rebase were removed so each language has a single definition.Why this approach
A client-side JSON collector (not SQL) matches the existing Query string format
{method, attribute?, values?}. Nesting the builder underQuerykeeps the public surface one type. Flattening inside the Client, without aquerieskey check, means any param that already carries query strings (join ON lists,having,or/and) is handled the same way.POST
/queryitself lives in Appwrite (appwrite/appwrite#13300, stacked on#11649). SDK methods will be generated ascreateQueryonce that spec is published.Verified
composer lint(phpcs PSR-12)composer lint-twig(djLint, 0 errors)composer refactor:check(Rector dry-run)composer test tests/e2e/PHP83Test.php— OK, 1511 assertions (includes array-enum scalar hydration)lib/+tests/tests.dartanalyze clean for the Builder/part-of/MockKind failuresquery.NewBuilder().Limit(1).Build()compiles and prints the expected limit queryNot verified
/queryround-trip from a generated SDK (depends on appwrite#13300 + spec publish).../govia areplaceingo.mod.twig(generated from the same spec as the CLI). Compiling the published CLI package against the last published Go module is not verified here.