diff --git a/templates/android/library/src/main/java/io/package/Query.kt.twig b/templates/android/library/src/main/java/io/package/Query.kt.twig index adf28d2741..cbf9a7d0f8 100644 --- a/templates/android/library/src/main/java/io/package/Query.kt.twig +++ b/templates/android/library/src/main/java/io/package/Query.kt.twig @@ -377,6 +377,15 @@ class Query( */ fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson>() }).toJson() + /** + * Constrain populated related documents on a relationship attribute. + * + * @param attribute The relationship attribute to query. + * @param queries Filter, order, select, and pagination queries applied to related documents. + * @returns The query string. + */ + fun relationship(attribute: String, queries: List) = Query("relationship", attribute, queries.map { it.fromJson>() }).toJson() + /** * Filter resources where attribute is at a specific distance from the given coordinates. * @@ -990,6 +999,8 @@ class Query( fun elemMatch(attribute: String, queries: List) = push(Query.elemMatch(attribute, queries)) + fun relationship(attribute: String, queries: List) = push(Query.relationship(attribute, queries)) + fun distanceEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceEqual(attribute, values, distance, meters)) fun distanceNotEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceNotEqual(attribute, values, distance, meters)) diff --git a/templates/dart/lib/query.dart.twig b/templates/dart/lib/query.dart.twig index 59d72be585..2753ba3c47 100644 --- a/templates/dart/lib/query.dart.twig +++ b/templates/dart/lib/query.dart.twig @@ -187,6 +187,16 @@ class Query { ).toString(); } + /// Constrain populated related documents on a relationship attribute. + /// + /// [attribute] The relationship attribute to query. + /// [queries] Filter, order, select, and pagination queries applied to related documents. + static String relationship(String attribute, List queries) => Query._( + 'relationship', + attribute, + queries.map((query) => jsonDecode(query)).toList(), + ).toString(); + /// Specify which attributes should be returned by the API call. static String select(List attributes) => Query._('select', null, attributes).toString(); diff --git a/templates/dart/lib/query/builder.dart.twig b/templates/dart/lib/query/builder.dart.twig index df819f3b2f..82d42c2dae 100644 --- a/templates/dart/lib/query/builder.dart.twig +++ b/templates/dart/lib/query/builder.dart.twig @@ -121,6 +121,9 @@ class QueryBuilder { QueryBuilder elemMatch(String attribute, List queries) => _push(Query.elemMatch(attribute, queries)); + QueryBuilder relationship(String attribute, List queries) => + _push(Query.relationship(attribute, queries)); + QueryBuilder distanceEqual( String attribute, List values, diff --git a/templates/dart/test/query_test.dart.twig b/templates/dart/test/query_test.dart.twig index 4e9eafcf62..7316c72ec5 100644 --- a/templates/dart/test/query_test.dart.twig +++ b/templates/dart/test/query_test.dart.twig @@ -316,6 +316,24 @@ void main() { expect(query['values'][1]['values'], [18]); }); + test('returns relationship', () { + final inner1 = Query.equal('approved', true); + final inner2 = Query.limit(2); + + final query = jsonDecode(Query.relationship('comments', [inner1, inner2])); + + expect(query['attribute'], 'comments'); + expect(query['method'], 'relationship'); + expect(query['values'].length, 2); + + expect(query['values'][0]['method'], 'equal'); + expect(query['values'][0]['attribute'], 'approved'); + expect(query['values'][0]['values'], [true]); + + expect(query['values'][1]['method'], 'limit'); + expect(query['values'][1]['values'], [2]); + }); + test('returns createdBefore', () { final query = jsonDecode(Query.createdBefore('2023-01-01')); expect(query['attribute'], '\$createdAt'); diff --git a/templates/deno/src/query.ts.twig b/templates/deno/src/query.ts.twig index 2d35a37d1c..ed6d56abdb 100644 --- a/templates/deno/src/query.ts.twig +++ b/templates/deno/src/query.ts.twig @@ -288,6 +288,20 @@ export class Query { queries.map((query) => JSON.parse(query)) ).toString(); + /** + * Constrain populated related documents on a relationship attribute. + * + * @param {string} attribute The relationship attribute to query. + * @param {string[]} queries Filter, order, select, and pagination queries applied to related documents. + * @returns {string} + */ + static relationship = (attribute: string, queries: string[]): string => + new Query( + "relationship", + attribute, + queries.map((query) => JSON.parse(query)) + ).toString(); + /** * Filter resources where attribute is at a specific distance from the given coordinates. * @@ -813,6 +827,10 @@ export namespace Query { return this.push(Query.elemMatch(attribute, queries)); } + relationship(attribute: string, queries: string[]): this { + return this.push(Query.relationship(attribute, queries)); + } + distanceEqual(attribute: string, values: any[], distance: number, meters: boolean = true): this { return this.push(Query.distanceEqual(attribute, values, distance, meters)); } diff --git a/templates/deno/test/query.test.ts.twig b/templates/deno/test/query.test.ts.twig index 3f221dec7a..7f49cbbd49 100644 --- a/templates/deno/test/query.test.ts.twig +++ b/templates/deno/test/query.test.ts.twig @@ -354,6 +354,22 @@ describe('Query', () => { assertEquals(parsed.values[1].values, [18]); }); + test('relationship', () => { + const inner1 = Query.equal('approved', true); + const inner2 = Query.limit(2); + const result = Query.relationship('comments', [inner1, inner2]); + const parsed = JSON.parse(result); + + assertEquals(parsed.method, 'relationship'); + assertEquals(parsed.attribute, 'comments'); + assertEquals(parsed.values.length, 2); + assertEquals(parsed.values[0].method, 'equal'); + assertEquals(parsed.values[0].attribute, 'approved'); + assertEquals(parsed.values[0].values, [true]); + assertEquals(parsed.values[1].method, 'limit'); + assertEquals(parsed.values[1].values, [2]); + }); + test('createdBefore', () => assertEquals( parsed(Query.createdBefore('2023-01-01').toString()), { diff --git a/templates/dotnet/Package/Query.cs.twig b/templates/dotnet/Package/Query.cs.twig index ed8c38e2c1..0d531bdbaa 100644 --- a/templates/dotnet/Package/Query.cs.twig +++ b/templates/dotnet/Package/Query.cs.twig @@ -306,6 +306,20 @@ namespace {{ spec.info.title | caseUcfirst }} return new Query("elemMatch", attribute, parsed).ToString(); } + /// + /// Constrain populated related documents on a relationship attribute. + /// + /// The relationship attribute to query. + /// Filter, order, select, and pagination queries applied to related documents. + /// The query string. + public static string Relationship(string attribute, List queries) + { + var parsed = queries + .Select(q => JsonSerializer.Deserialize(q, Client.DeserializerOptions) as object) + .ToList(); + return new Query("relationship", attribute, parsed).ToString(); + } + public static string DistanceEqual(string attribute, object values, double distance, bool meters = true) { return new Query("distanceEqual", attribute, new List { new List { values, distance, meters } }).ToString(); diff --git a/templates/dotnet/Package/Query/Builder.cs.twig b/templates/dotnet/Package/Query/Builder.cs.twig index 00fb5f0857..8e31e5b95b 100644 --- a/templates/dotnet/Package/Query/Builder.cs.twig +++ b/templates/dotnet/Package/Query/Builder.cs.twig @@ -236,6 +236,11 @@ namespace {{ spec.info.title | caseUcfirst }} return Push(Query.ElemMatch(attribute, queries)); } + public Builder Relationship(string attribute, List queries) + { + return Push(Query.Relationship(attribute, queries)); + } + public Builder DistanceEqual(string attribute, object values, double distance, bool meters = true) { return Push(Query.DistanceEqual(attribute, values, distance, meters)); diff --git a/templates/go/query.go.twig b/templates/go/query.go.twig index ea094cb9fd..8b9468df18 100644 --- a/templates/go/query.go.twig +++ b/templates/go/query.go.twig @@ -523,6 +523,27 @@ func ElemMatch(attribute string, queries []string) string { }) } +// Relationship constrains populated related documents on a relationship attribute. +// +// attribute: The relationship attribute to query. +// queries: Filter, order, select, and pagination queries applied to related documents. +// Returns the query string. +func Relationship(attribute string, queries []string) string { + var parsedQueries []interface{} + for _, query := range queries { + var q interface{} + if err := json.Unmarshal([]byte(query), &q); err != nil { + continue + } + parsedQueries = append(parsedQueries, q) + } + return parseQuery(queryOptions{ + Method: "relationship", + Attribute: &attribute, + Values: &parsedQueries, + }) +} + func aliasedQuery(method, attribute, alias string) string { options := queryOptions{ Method: method, diff --git a/templates/go/query/builder.go.twig b/templates/go/query/builder.go.twig index ae7fcc7a4e..5b49d1d901 100644 --- a/templates/go/query/builder.go.twig +++ b/templates/go/query/builder.go.twig @@ -179,6 +179,10 @@ func (b *Builder) ElemMatch(attribute string, queries []string) *Builder { return b.push(ElemMatch(attribute, queries)) } +func (b *Builder) Relationship(attribute string, queries []string) *Builder { + return b.push(Relationship(attribute, queries)) +} + func (b *Builder) DistanceEqual(attribute string, values []interface{}, distance float64, meters bool) *Builder { return b.push(DistanceEqual(attribute, values, distance, meters)) } diff --git a/templates/go/query_test.go.twig b/templates/go/query_test.go.twig index 48af42d692..bcd69f54c4 100644 --- a/templates/go/query_test.go.twig +++ b/templates/go/query_test.go.twig @@ -126,6 +126,28 @@ func TestOr(t *testing.T) { } } +func TestRelationship(t *testing.T) { + q1 := Equal("approved", true) + q2 := Limit(2) + q := Relationship("comments", []string{q1, q2}) + + var data map[string]interface{} + if err := json.Unmarshal([]byte(q), &data); err != nil { + t.Fatal(err) + } + + if data["method"] != "relationship" { + t.Errorf("Expected relationship, got %v", data["method"]) + } + if data["attribute"] != "comments" { + t.Errorf("Expected comments, got %v", data["attribute"]) + } + values := data["values"].([]interface{}) + if len(values) != 2 { + t.Errorf("Expected 2 subqueries, got %d", len(values)) + } +} + func TestCountWithAlias(t *testing.T) { q := Count("*", "total") var data map[string]interface{} diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig index 6b122448be..e50871e430 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig @@ -133,6 +133,15 @@ class Query( */ fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson() }).toJson() + /** + * Constrain populated related documents on a relationship attribute. + * + * @param attribute The relationship attribute to query. + * @param queries Filter, order, select, and pagination queries applied to related documents. + * @returns The query string. + */ + fun relationship(attribute: String, queries: List) = Query("relationship", attribute, queries.map { it.fromJson() }).toJson() + /** * Filter resources where attribute is at a specific distance from the given coordinates. * @@ -684,6 +693,8 @@ class Query( fun elemMatch(attribute: String, queries: List) = push(Query.elemMatch(attribute, queries)) + fun relationship(attribute: String, queries: List) = push(Query.relationship(attribute, queries)) + fun distanceEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceEqual(attribute, values, distance, meters)) fun distanceNotEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceNotEqual(attribute, values, distance, meters)) diff --git a/templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig b/templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig index 72588a736c..73e7a2220c 100644 --- a/templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig +++ b/templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig @@ -110,6 +110,18 @@ class QueryTest { assertEquals("on", method(Query.on("\$id", "customerId"))) } + @Test + fun relationship() { + val query = Query.relationship("comments", listOf(Query.equal("approved", true), Query.limit(2))) + val json = JsonParser.parseString(query).asJsonObject + assertEquals("relationship", json.get("method").asString) + assertEquals("comments", json.get("attribute").asString) + val values = json.get("values").asJsonArray + assertEquals("equal", values[0].asJsonObject.get("method").asString) + assertEquals("limit", values[1].asJsonObject.get("method").asString) + assertEquals(listOf(query), Query.builder().relationship("comments", listOf(Query.equal("approved", true), Query.limit(2))).build()) + } + @Test fun nestedLeftJoin() { val json = JsonParser.parseString( diff --git a/templates/php/src/Query.php.twig b/templates/php/src/Query.php.twig index 6ecd9d618f..9911f7c326 100644 --- a/templates/php/src/Query.php.twig +++ b/templates/php/src/Query.php.twig @@ -378,6 +378,19 @@ class Query implements \JsonSerializable, \Stringable return new Query('elemMatch', $attribute, $queries)->__toString(); } + /** + * Constrain populated related documents on a relationship attribute. + * + */ + public static function relationship(string $attribute, array $queries): string + { + foreach ($queries as &$query) { + $query = \json_decode($query, true); + } + + return new Query('relationship', $attribute, $queries)->__toString(); + } + /** * Distance Equal */ diff --git a/templates/php/src/Query/Builder.php.twig b/templates/php/src/Query/Builder.php.twig index 8e0f50d7cf..da411c6160 100644 --- a/templates/php/src/Query/Builder.php.twig +++ b/templates/php/src/Query/Builder.php.twig @@ -243,6 +243,11 @@ class Builder return $this->push(Query::elemMatch($attribute, $queries)); } + public function relationship(string $attribute, array $queries): self + { + return $this->push(Query::relationship($attribute, $queries)); + } + /** * @param array $values */ diff --git a/templates/php/tests/QueryTest.php.twig b/templates/php/tests/QueryTest.php.twig index 894b8bdf35..60fdd5554a 100644 --- a/templates/php/tests/QueryTest.php.twig +++ b/templates/php/tests/QueryTest.php.twig @@ -311,6 +311,25 @@ final class QueryTest extends TestCase $this->assertSame([18], $query['values'][1]['values']); } + public function testRelationship(): void + { + $inner1 = Query::equal('approved', true); + $inner2 = Query::limit(2); + + $query = json_decode(Query::relationship('comments', [$inner1, $inner2]), true); + + $this->assertSame('comments', $query['attribute']); + $this->assertSame('relationship', $query['method']); + $this->assertCount(2, $query['values']); + + $this->assertSame('equal', $query['values'][0]['method']); + $this->assertSame('approved', $query['values'][0]['attribute']); + $this->assertSame([true], $query['values'][0]['values']); + + $this->assertSame('limit', $query['values'][1]['method']); + $this->assertSame([2], $query['values'][1]['values']); + } + public function testCreatedBefore(): void { $query = json_decode(Query::createdBefore('2023-01-01'), true); diff --git a/templates/python/package/query.py.twig b/templates/python/package/query.py.twig index 261845d484..f32bdc9e1d 100644 --- a/templates/python/package/query.py.twig +++ b/templates/python/package/query.py.twig @@ -229,6 +229,20 @@ class Query: """ return str(Query("elemMatch", attribute, [json.loads(query) for query in queries])) + @staticmethod + def relationship(attribute, queries): + """ + Constrain populated related documents on a relationship attribute. + + Args: + attribute: The relationship attribute to query. + queries: Filter, order, select, and pagination queries applied to related documents. + + Returns: + The query string. + """ + return str(Query("relationship", attribute, [json.loads(query) for query in queries])) + @staticmethod def distance_equal(attribute, values, distance, meters=True): return str(Query("distanceEqual", attribute, [[values, distance, meters]])) @@ -594,6 +608,9 @@ class Query: def elem_match(self, attribute, queries): return self._push(Query.elem_match(attribute, queries)) + def relationship(self, attribute, queries): + return self._push(Query.relationship(attribute, queries)) + def distance_equal(self, attribute, values, distance, meters=True): return self._push(Query.distance_equal(attribute, values, distance, meters)) diff --git a/templates/react-native/src/query.ts.twig b/templates/react-native/src/query.ts.twig index be2dff7077..cd2d7855ec 100644 --- a/templates/react-native/src/query.ts.twig +++ b/templates/react-native/src/query.ts.twig @@ -310,6 +310,20 @@ export class Query { queries.map((query) => JSONbig.parse(query)), ).toString(); + /** + * Constrain populated related documents on a relationship attribute. + * + * @param {string} attribute The relationship attribute to query. + * @param {string[]} queries Filter, order, select, and pagination queries applied to related documents. + * @returns {string} + */ + static relationship = (attribute: string, queries: string[]): string => + new Query( + 'relationship', + attribute, + queries.map((query) => JSONbig.parse(query)), + ).toString(); + /** * Filter resources where attribute is at a specific distance from the given coordinates. * @@ -960,6 +974,10 @@ export namespace Query { return this.push(Query.elemMatch(attribute, queries)); } + relationship(attribute: string, queries: string[]): this { + return this.push(Query.relationship(attribute, queries)); + } + distanceEqual( attribute: string, values: any[], diff --git a/templates/ruby/lib/container/query.rb.twig b/templates/ruby/lib/container/query.rb.twig index a353ef1cdf..12cd5a6490 100644 --- a/templates/ruby/lib/container/query.rb.twig +++ b/templates/ruby/lib/container/query.rb.twig @@ -216,6 +216,15 @@ module {{spec.info.title | caseUcfirst}} return Query.new("elemMatch", attribute, queries.map { |query| JSON.parse(query) }).to_s end + # Constrain populated related documents on a relationship attribute. + # + # @param attribute [String] The relationship attribute to query. + # @param queries [Array] Filter, order, select, and pagination queries applied to related documents. + # @return [String] The query string. + def relationship(attribute, queries) + return Query.new("relationship", attribute, queries.map { |query| JSON.parse(query) }).to_s + end + def distance_equal(attribute, values, distance, meters = true) return Query.new("distanceEqual", attribute, [[values, distance, meters]]).to_s end diff --git a/templates/ruby/lib/container/query/builder.rb.twig b/templates/ruby/lib/container/query/builder.rb.twig index 7690e5b38b..d3c3a09058 100644 --- a/templates/ruby/lib/container/query/builder.rb.twig +++ b/templates/ruby/lib/container/query/builder.rb.twig @@ -170,6 +170,10 @@ module {{spec.info.title | caseUcfirst}} return push(Query.elem_match(attribute, queries)) end + def relationship(attribute, queries) + return push(Query.relationship(attribute, queries)) + end + def distance_equal(attribute, values, distance, meters = true) return push(Query.distance_equal(attribute, values, distance, meters)) end diff --git a/templates/rust/src/query/builder.rs.twig b/templates/rust/src/query/builder.rs.twig index f0e780be64..ea55faddab 100644 --- a/templates/rust/src/query/builder.rs.twig +++ b/templates/rust/src/query/builder.rs.twig @@ -366,6 +366,16 @@ impl Builder { self } + pub fn relationship(mut self, attribute: S, queries: I) -> Self + where + S: Into, + I: IntoIterator, + Q: AsRef, + { + self.queries.push(Query::relationship(attribute, queries)); + self + } + pub fn count(mut self, attribute: impl Into, alias: impl Into) -> Self { self.queries.push(Query::count(attribute, alias)); self diff --git a/templates/rust/src/query/mod.rs.twig b/templates/rust/src/query/mod.rs.twig index f74d033d6f..ce5b494786 100644 --- a/templates/rust/src/query/mod.rs.twig +++ b/templates/rust/src/query/mod.rs.twig @@ -461,6 +461,19 @@ impl Query { Self::new("elemMatch".to_string(), Some(attribute.into()), values) } + pub fn relationship(attribute: S, queries: I) -> Self + where + S: Into, + I: IntoIterator, + Q: AsRef, + { + let values: Vec = queries + .into_iter() + .filter_map(|query| serde_json::from_str(query.as_ref()).ok()) + .collect(); + Self::new("relationship".to_string(), Some(attribute.into()), values) + } + pub fn count(attribute: impl Into, alias: impl Into) -> Self { Self::aliased("count", attribute, alias) } diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index 2020acd9a9..6c55ff9711 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -334,6 +334,10 @@ async fn test_queries(client: &Client) -> Result<(), Box> Query::equal("name", "Alice").to_string(), Query::greater_than("age", 18).to_string(), ])); + println!("{}", Query::relationship("comments", vec![ + Query::equal("approved", true).to_string(), + Query::limit(2).to_string(), + ])); let general = General::new(client); let mut queries: Vec = vec![ Query::count("*", "total").to_string(), diff --git a/templates/skills/dart.md.twig b/templates/skills/dart.md.twig index 13abc23ae7..8cfd2dbb6a 100644 --- a/templates/skills/dart.md.twig +++ b/templates/skills/dart.md.twig @@ -189,6 +189,7 @@ Query.cursorBefore('[ROW_ID]') Query.select(['field1', 'field2']) // return only specified fields Query.or([Query.equal('a', 1), Query.equal('b', 2)]) // OR Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)]) // AND (default) +Query.relationship('comments', [Query.equal('approved', true), Query.limit(25)]) // related documents // Aggregations (optional alias) Query.count('*', 'total') diff --git a/templates/skills/dotnet.md.twig b/templates/skills/dotnet.md.twig index 3681bed2af..570f7e80dd 100644 --- a/templates/skills/dotnet.md.twig +++ b/templates/skills/dotnet.md.twig @@ -132,6 +132,7 @@ Query.CursorBefore("[ROW_ID]") Query.Select(new List { "field1", "field2" }) Query.Or(new List { Query.Equal("a", 1), Query.Equal("b", 2) }) // OR Query.And(new List { Query.GreaterThan("age", 18), Query.LessThan("age", 65) }) // AND (default) +Query.Relationship("comments", new List { Query.Equal("approved", true), Query.Limit(25) }) // related documents // Aggregations (optional alias) Query.Count("*", "total") diff --git a/templates/skills/go.md.twig b/templates/skills/go.md.twig index 8d67a9b331..8e98b4a562 100644 --- a/templates/skills/go.md.twig +++ b/templates/skills/go.md.twig @@ -157,6 +157,7 @@ query.CursorBefore("[ROW_ID]") query.Select([]string{"field1", "field2"}) query.Or([]string{query.Equal("a", 1), query.Equal("b", 2)}) // OR query.And([]string{query.GreaterThan("age", 18), query.LessThan("age", 65)}) // AND (default) +query.Relationship("comments", []string{query.Equal("approved", true), query.Limit(25)}) // related documents // Aggregations (optional alias) query.Count("*", "total") diff --git a/templates/skills/kotlin.md.twig b/templates/skills/kotlin.md.twig index bdf12deaa3..6e8b6b292a 100644 --- a/templates/skills/kotlin.md.twig +++ b/templates/skills/kotlin.md.twig @@ -213,6 +213,7 @@ Query.cursorBefore("[ROW_ID]") Query.select(listOf("field1", "field2")) Query.or(listOf(Query.equal("a", 1), Query.equal("b", 2))) // OR Query.and(listOf(Query.greaterThan("age", 18), Query.lessThan("age", 65))) // AND (default) +Query.relationship("comments", listOf(Query.equal("approved", true), Query.limit(25))) // related documents // Aggregations (optional alias) Query.count("*", "total") diff --git a/templates/skills/php.md.twig b/templates/skills/php.md.twig index 0933e68874..dc72d74699 100644 --- a/templates/skills/php.md.twig +++ b/templates/skills/php.md.twig @@ -140,6 +140,7 @@ Query::cursorBefore('[ROW_ID]') Query::select(['field1', 'field2']) // return only specified fields Query::or([Query::equal('a', [1]), Query::equal('b', [2])]) // OR Query::and([Query::greaterThan('age', 18), Query::lessThan('age', 65)]) // AND (default) +Query::relationship('comments', [Query::equal('approved', [true]), Query::limit(25)]) // related documents // Aggregations (optional alias) Query::count('*', 'total') diff --git a/templates/skills/python.md.twig b/templates/skills/python.md.twig index 38430bb0fb..f35ca12b0a 100644 --- a/templates/skills/python.md.twig +++ b/templates/skills/python.md.twig @@ -147,6 +147,7 @@ Query.cursor_before('[ROW_ID]') Query.select(['field1', 'field2']) # return only specified fields Query.or_queries([Query.equal('a', 1), Query.equal('b', 2)]) # OR Query.and_queries([Query.greater_than('age', 18), Query.less_than('age', 65)]) # AND (default) +Query.relationship('comments', [Query.equal('approved', True), Query.limit(25)]) # related documents # Aggregations (optional alias) Query.count('*', 'total') diff --git a/templates/skills/ruby.md.twig b/templates/skills/ruby.md.twig index 2280e6a122..2438ef380a 100644 --- a/templates/skills/ruby.md.twig +++ b/templates/skills/ruby.md.twig @@ -147,6 +147,7 @@ Query.cursor_before('[ROW_ID]') Query.select(['field1', 'field2']) # return only specified fields Query.or([Query.equal('a', 1), Query.equal('b', 2)]) # OR Query.and([Query.greater_than('age', 18), Query.less_than('age', 65)]) # AND (default) +Query.relationship('comments', [Query.equal('approved', true), Query.limit(25)]) # related documents # Aggregations (optional alias) Query.count('*', 'total') diff --git a/templates/skills/rust.md.twig b/templates/skills/rust.md.twig index ea7b918e0f..e5a728c209 100644 --- a/templates/skills/rust.md.twig +++ b/templates/skills/rust.md.twig @@ -273,6 +273,15 @@ let multi_value = Query::equal( ]), ) .to_string(); + +let related = Query::relationship( + "comments", + vec![ + Query::equal("approved", true).to_string(), + Query::limit(25).to_string(), + ], +) +.to_string(); ``` ### File Storage diff --git a/templates/skills/swift.md.twig b/templates/skills/swift.md.twig index 87f01d3e8b..d1f0cb5d97 100644 --- a/templates/skills/swift.md.twig +++ b/templates/skills/swift.md.twig @@ -171,6 +171,7 @@ Query.cursorBefore("[ROW_ID]") Query.select(["field1", "field2"]) Query.or([Query.equal("a", value: 1), Query.equal("b", value: 2)]) // OR Query.and([Query.greaterThan("age", value: 18), Query.lessThan("age", value: 65)]) // AND (default) +Query.relationship("comments", queries: [Query.equal("approved", value: true), Query.limit(25)]) // related documents // Aggregations (optional alias) Query.count("*", alias: "total") diff --git a/templates/skills/typescript.md.twig b/templates/skills/typescript.md.twig index 4b0def9081..818cc16904 100644 --- a/templates/skills/typescript.md.twig +++ b/templates/skills/typescript.md.twig @@ -330,6 +330,7 @@ Query.select(['field1', 'field2']) // return only specified fields // Logical Query.or([Query.equal('a', 1), Query.equal('b', 2)]) // OR condition Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)]) // explicit AND (queries are AND by default) +Query.relationship('comments', [Query.equal('approved', true), Query.limit(25)]) // related documents // Aggregations (optional alias) Query.count('*', 'total') diff --git a/templates/swift/Sources/Query.swift.twig b/templates/swift/Sources/Query.swift.twig index 9a6d1f2fde..d4d39e1393 100644 --- a/templates/swift/Sources/Query.swift.twig +++ b/templates/swift/Sources/Query.swift.twig @@ -520,6 +520,28 @@ public struct Query: Codable, CustomStringConvertible { ).description } + /// Constrain populated related documents on a relationship attribute. + /// + /// - Parameters: + /// - attribute: The relationship attribute to query. + /// - queries: Filter, order, select, and pagination queries applied to related documents. + /// - Returns: The query string. + public static func relationship(_ attribute: String, queries: [String]) -> String { + let decoder = JSONDecoder() + let decodedQueries = queries.compactMap { queryStr -> Query? in + guard let data = queryStr.data(using: .utf8) else { + return nil + } + return try? decoder.decode(Query.self, from: data) + } + + return Query( + method: "relationship", + attribute: attribute, + values: decodedQueries + ).description + } + public static func distanceEqual( _ attribute: String, values: [Any], distance: Double, meters: Bool = true ) -> String { diff --git a/templates/swift/Sources/Query/Builder.swift.twig b/templates/swift/Sources/Query/Builder.swift.twig index 0d950f92ad..cf5de836b0 100644 --- a/templates/swift/Sources/Query/Builder.swift.twig +++ b/templates/swift/Sources/Query/Builder.swift.twig @@ -231,6 +231,10 @@ extension Query { return push(Query.elemMatch(attribute, queries: queries)) } + public func relationship(_ attribute: String, queries: [String]) -> Builder { + return push(Query.relationship(attribute, queries: queries)) + } + @discardableResult public func distanceEqual(_ attribute: String, values: [Any], distance: Double, meters: Bool = true) -> Builder { return push(Query.distanceEqual(attribute, values: values, distance: distance, meters: meters)) diff --git a/templates/web/src/query.ts.twig b/templates/web/src/query.ts.twig index f4c2447a1c..6cce10a8b4 100644 --- a/templates/web/src/query.ts.twig +++ b/templates/web/src/query.ts.twig @@ -468,6 +468,20 @@ export class Query { queries.map((query) => JSONbig.parse(query)), ).toString(); + /** + * Constrain populated related documents on a relationship attribute. + * + * @param {string} attribute The relationship attribute to query. + * @param {string[]} queries Filter, order, select, and pagination queries applied to related documents. + * @returns {string} + */ + static relationship = (attribute: string, queries: string[]): string => + new Query( + 'relationship', + attribute, + queries.map((query) => JSONbig.parse(query)), + ).toString(); + /** * Filter resources where attribute is at a specific distance from the given coordinates. * @@ -1141,6 +1155,10 @@ export namespace Query { return this.push(Query.elemMatch(attribute, queries)); } + relationship(attribute: string, queries: string[]): this { + return this.push(Query.relationship(attribute, queries)); + } + distanceEqual( attribute: string, values: any[], diff --git a/tests/e2e/Base.php b/tests/e2e/Base.php index e36bb1beab..87354360fc 100644 --- a/tests/e2e/Base.php +++ b/tests/e2e/Base.php @@ -440,6 +440,7 @@ abstract class Base extends TestCase '{"method":"exists","values":["attr1","attr2"]}', '{"method":"notExists","values":["attr1","attr2"]}', '{"method":"elemMatch","attribute":"friends","values":[{"method":"equal","attribute":"name","values":["Alice"]},{"method":"greaterThan","attribute":"age","values":[18]}]}', + '{"method":"relationship","attribute":"comments","values":[{"method":"equal","attribute":"approved","values":[true]},{"method":"limit","values":[2]}]}', ]; // Each language harness sends these through a real listRows request and prints diff --git a/tests/e2e/languages/android/Tests.kt b/tests/e2e/languages/android/Tests.kt index 6ba77aa903..acda8ca9d0 100644 --- a/tests/e2e/languages/android/Tests.kt +++ b/tests/e2e/languages/android/Tests.kt @@ -458,6 +458,10 @@ class ServiceTest { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ))) + writeToFile(Query.relationship("comments", listOf( + Query.equal("approved", true), + Query.limit(2) + ))) val queryTransport = general.listRows( listOf( Query.count("*", "total"), diff --git a/tests/e2e/languages/apple/Tests.swift b/tests/e2e/languages/apple/Tests.swift index 61eabd6251..218f5b310e 100644 --- a/tests/e2e/languages/apple/Tests.swift +++ b/tests/e2e/languages/apple/Tests.swift @@ -431,6 +431,10 @@ class Tests: XCTestCase { Query.equal("name", value: "Alice"), Query.greaterThan("age", value: 18) ])) + print(Query.relationship("comments", queries: [ + Query.equal("approved", value: true), + Query.limit(2) + ])) let transportQueries: [String] = [ Query.count("*", alias: "total"), Query.join("orders", left: "$id", right: "customerId"), diff --git a/tests/e2e/languages/dart/tests.dart b/tests/e2e/languages/dart/tests.dart index 717e490450..ee06d19b92 100644 --- a/tests/e2e/languages/dart/tests.dart +++ b/tests/e2e/languages/dart/tests.dart @@ -279,6 +279,11 @@ void main() async { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ])); + print(Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) + ])); + final queryTransport = await general.listRows(queries: [ Query.count('*', 'total'), Query.join('orders', '\$id', 'customerId'), diff --git a/tests/e2e/languages/deno/tests.ts b/tests/e2e/languages/deno/tests.ts index bbf7492bb8..3a5fc7042b 100644 --- a/tests/e2e/languages/deno/tests.ts +++ b/tests/e2e/languages/deno/tests.ts @@ -305,6 +305,10 @@ async function start() { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ])); + console.log(Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) + ])); const queryTransport = await general.listRows([ Query.count("*", "total"), Query.join("orders", "$id", "customerId"), diff --git a/tests/e2e/languages/dotnet/Tests.cs b/tests/e2e/languages/dotnet/Tests.cs index 33cc1e86fc..8be323f536 100644 --- a/tests/e2e/languages/dotnet/Tests.cs +++ b/tests/e2e/languages/dotnet/Tests.cs @@ -287,6 +287,10 @@ public async Task Test1() Query.Equal("name", "Alice"), Query.GreaterThan("age", 18) })); + TestContext.WriteLine(Query.Relationship("comments", new List { + Query.Equal("approved", true), + Query.Limit(2) + })); var transportQueries = new List { Query.Count("*", "total"), diff --git a/tests/e2e/languages/flutter/tests.dart b/tests/e2e/languages/flutter/tests.dart index 5789113685..1d2ff26b2e 100644 --- a/tests/e2e/languages/flutter/tests.dart +++ b/tests/e2e/languages/flutter/tests.dart @@ -427,6 +427,10 @@ void main() async { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ])); + print(Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) + ])); final queryTransport = await general.listRows(queries: [ Query.count('*', 'total'), Query.join('orders', '\$id', 'customerId'), diff --git a/tests/e2e/languages/go-v2/tests.go b/tests/e2e/languages/go-v2/tests.go index 1295739f06..2c58c622a6 100644 --- a/tests/e2e/languages/go-v2/tests.go +++ b/tests/e2e/languages/go-v2/tests.go @@ -404,6 +404,10 @@ func testQueries(client client.Client) { query.Equal("name", "Alice"), query.GreaterThan("age", 18), })) + fmt.Println(query.Relationship("comments", []string{ + query.Equal("approved", true), + query.Limit(2), + })) general := appwrite.NewGeneral(client) queries := []string{ query.Count("*", "total"), diff --git a/tests/e2e/languages/go/tests.go b/tests/e2e/languages/go/tests.go index 2406898e8c..d6db264ba0 100644 --- a/tests/e2e/languages/go/tests.go +++ b/tests/e2e/languages/go/tests.go @@ -404,6 +404,10 @@ func testQueries(client client.Client) { query.Equal("name", "Alice"), query.GreaterThan("age", 18), })) + fmt.Println(query.Relationship("comments", []string{ + query.Equal("approved", true), + query.Limit(2), + })) general := appwrite.NewGeneral(client) queries := []string{ query.Count("*", "total"), diff --git a/tests/e2e/languages/kotlin/Tests.kt b/tests/e2e/languages/kotlin/Tests.kt index 53d60a872b..cc301a4d29 100644 --- a/tests/e2e/languages/kotlin/Tests.kt +++ b/tests/e2e/languages/kotlin/Tests.kt @@ -303,6 +303,10 @@ class ServiceTest { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ))) + writeToFile(Query.relationship("comments", listOf( + Query.equal("approved", true), + Query.limit(2) + ))) val queryTransport = general.listRows( listOf( Query.count("*", "total"), diff --git a/tests/e2e/languages/node/test.js b/tests/e2e/languages/node/test.js index 07a7947d46..97b905d91e 100644 --- a/tests/e2e/languages/node/test.js +++ b/tests/e2e/languages/node/test.js @@ -413,6 +413,10 @@ async function start() { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ])); + console.log(Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) + ])); const queryTransport = await general.listRows([ Query.count("*", "total"), Query.join("orders", "$id", "customerId"), diff --git a/tests/e2e/languages/php/test.php b/tests/e2e/languages/php/test.php index 1ee2bb0fc8..88b6959076 100644 --- a/tests/e2e/languages/php/test.php +++ b/tests/e2e/languages/php/test.php @@ -393,6 +393,10 @@ public function toArray(): array Query::equal('name', ['Alice']), Query::greaterThan('age', 18) ]) . "\n"; +echo Query::relationship('comments', [ + Query::equal('approved', [true]), + Query::limit(2) +]) . "\n"; $queryTransport = $general->listRows([ Query::count('*', 'total'), Query::join('orders', '$id', 'customerId'), diff --git a/tests/e2e/languages/python/tests.py b/tests/e2e/languages/python/tests.py index dafa69e5cb..78bd0c9d2c 100644 --- a/tests/e2e/languages/python/tests.py +++ b/tests/e2e/languages/python/tests.py @@ -251,6 +251,10 @@ Query.equal("name", "Alice"), Query.greater_than("age", 18) ])) +print(Query.relationship("comments", [ + Query.equal("approved", True), + Query.limit(2) +])) query_transport = general.list_rows([ Query.count("*", "total"), Query.join("orders", "$id", "customerId"), diff --git a/tests/e2e/languages/react-native/browser.js b/tests/e2e/languages/react-native/browser.js index 362941caad..4c1f681afa 100644 --- a/tests/e2e/languages/react-native/browser.js +++ b/tests/e2e/languages/react-native/browser.js @@ -391,6 +391,10 @@ import { Query.equal('name', 'Alice'), Query.greaterThan('age', 18), ])); + console.log(Query.relationship('comments', [ + Query.equal('approved', true), + Query.limit(2), + ])); const queryTransport = await general.listRows([ Query.count('*', 'total'), Query.join('orders', '$id', 'customerId'), diff --git a/tests/e2e/languages/ruby/tests.rb b/tests/e2e/languages/ruby/tests.rb index cf0fd9cddb..325d5765f0 100644 --- a/tests/e2e/languages/ruby/tests.rb +++ b/tests/e2e/languages/ruby/tests.rb @@ -264,6 +264,10 @@ Query.equal("name", "Alice"), Query.greater_than("age", 18) ]) +puts Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) +]) query_transport = general.list_rows(queries: [ Query.count('*', 'total'), Query.join('orders', '$id', 'customerId'), diff --git a/tests/e2e/languages/swift/Tests.swift b/tests/e2e/languages/swift/Tests.swift index 4fef1d8d09..c9986ebf87 100644 --- a/tests/e2e/languages/swift/Tests.swift +++ b/tests/e2e/languages/swift/Tests.swift @@ -310,6 +310,10 @@ class Tests: XCTestCase { Query.equal("name", value: "Alice"), Query.greaterThan("age", value: 18) ])) + print(Query.relationship("comments", queries: [ + Query.equal("approved", value: true), + Query.limit(2) + ])) let transportQueries: [String] = [ Query.count("*", alias: "total"), Query.join("orders", left: "$id", right: "customerId"), diff --git a/tests/e2e/languages/unity/Tests.cs b/tests/e2e/languages/unity/Tests.cs index 976811a852..e5ec083ea8 100644 --- a/tests/e2e/languages/unity/Tests.cs +++ b/tests/e2e/languages/unity/Tests.cs @@ -421,6 +421,10 @@ private async Task RunAsyncTest() Query.Equal("name", "Alice"), Query.GreaterThan("age", 18) })); + LogResult(Query.Relationship("comments", new List { + Query.Equal("approved", true), + Query.Limit(2) + })); var transportQueries = new List { Query.Count("*", "total"), diff --git a/tests/e2e/languages/web/index.html b/tests/e2e/languages/web/index.html index b96258e899..f5b014e7a5 100644 --- a/tests/e2e/languages/web/index.html +++ b/tests/e2e/languages/web/index.html @@ -509,6 +509,10 @@ Query.equal("name", "Alice"), Query.greaterThan("age", 18) ])); + console.log(Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) + ])); const queryTransport = await general.listRows([ Query.count("*", "total"), Query.join("orders", "$id", "customerId"), diff --git a/tests/e2e/languages/web/node.js b/tests/e2e/languages/web/node.js index 31d7cdf787..d037538092 100644 --- a/tests/e2e/languages/web/node.js +++ b/tests/e2e/languages/web/node.js @@ -316,6 +316,10 @@ async function start() { Query.equal("name", "Alice"), Query.greaterThan("age", 18) ])); + console.log(Query.relationship("comments", [ + Query.equal("approved", true), + Query.limit(2) + ])); const queryTransport = await general.listRows([ Query.count("*", "total"), Query.join("orders", "$id", "customerId"),