From f99c4c2db4e6d4227de7a40a9cd094f1d48482d8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 16 Oct 2026 23:51:02 +1300 Subject: [PATCH 1/2] docs(query): document the join alias default and population stddev The server now names an alias-less join after its table ID, so every SDK's alias-less join forms (column form, ON-list form, crossJoin) are first-class rather than rejected. The join docblocks say so, and each SDK's query tests pin that those forms send no alias and leave the default to the server. stddev and variance map to STDDEV_POP and VAR_POP on every engine, so their docblocks now say population instead of leaving it ambiguous. The deno templates are left alone: deno is no longer a generator target. Co-Authored-By: Claude Opus 5.5 --- .../src/main/java/io/package/Query.kt.twig | 32 +++++++++---------- templates/dart/lib/query.dart.twig | 14 ++++---- templates/dart/test/query_test.dart.twig | 17 ++++++++++ templates/go/query_test.go.twig | 32 +++++++++++++++++++ .../src/main/kotlin/io/appwrite/Query.kt.twig | 32 +++++++++---------- .../test/kotlin/io/appwrite/QueryTest.kt.twig | 20 ++++++++++++ templates/node/test/query.test.js.twig | 17 ++++++++++ templates/php/src/Query.php.twig | 14 ++++---- templates/php/tests/QueryTest.php.twig | 17 ++++++++++ templates/python/test/test_query.py.twig | 13 ++++++++ templates/react-native/src/query.ts.twig | 2 +- templates/swift/Tests/Tests.swift.twig | 18 +++++++++++ templates/web/src/query.ts.twig | 14 ++++---- 13 files changed, 188 insertions(+), 54 deletions(-) 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 fc80986097..d781241900 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 @@ -584,7 +584,7 @@ class Query( fun max(attribute: String, alias: String = "") = Query("max", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** - * Standard deviation of [attribute], optionally aliased. + * Population standard deviation of [attribute] (the same as stddevPop), optionally aliased. * * @param attribute The attribute to inspect. * @param alias Optional result alias. @@ -611,7 +611,7 @@ class Query( fun stddevSamp(attribute: String, alias: String = "") = Query("stddevSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** - * Variance of [attribute], optionally aliased. + * Population variance of [attribute] (the same as varPop), optionally aliased. * * @param attribute The attribute to inspect. * @param alias Optional result alias. @@ -698,19 +698,19 @@ class Query( fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() /** - * Inner join. [table] is the joined collection/table ID. + * Inner join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) /** - * Inner join with nested ON queries and no alias. + * Inner join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -729,19 +729,19 @@ class Query( fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) /** - * Left join. [table] is the joined collection/table ID. + * Left join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) /** - * Left join with nested ON queries and no alias. + * Left join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -760,19 +760,19 @@ class Query( fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) /** - * Right join. [table] is the joined collection/table ID. + * Right join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) /** - * Right join with nested ON queries and no alias. + * Right join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -791,19 +791,19 @@ class Query( fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) /** - * Full outer join. [table] is the joined collection/table ID. + * Full outer join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) /** - * Full outer join with nested ON queries and no alias. + * Full outer join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -822,10 +822,10 @@ class Query( fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) /** - * Cross join. [table] is the joined collection/table ID. + * Cross join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() diff --git a/templates/dart/lib/query.dart.twig b/templates/dart/lib/query.dart.twig index 2753ba3c47..c2b25e1fe8 100644 --- a/templates/dart/lib/query.dart.twig +++ b/templates/dart/lib/query.dart.twig @@ -353,7 +353,7 @@ class Query { static String max(String attribute, [String alias = '']) => Query._('max', attribute, alias.isNotEmpty ? alias : null).toString(); - /// Standard deviation of [attribute], optionally aliased. + /// Population standard deviation of [attribute] (the same as stddevPop), optionally aliased. static String stddev(String attribute, [String alias = '']) => Query._('stddev', attribute, alias.isNotEmpty ? alias : null).toString(); @@ -367,7 +367,7 @@ class Query { Query._('stddevSamp', attribute, alias.isNotEmpty ? alias : null) .toString(); - /// Variance of [attribute], optionally aliased. + /// Population variance of [attribute] (the same as varPop), optionally aliased. static String variance(String attribute, [String alias = '']) => Query._('variance', attribute, alias.isNotEmpty ? alias : null) .toString(); @@ -410,7 +410,7 @@ class Query { static String on(String left, String right, [String operator = '=']) => Query._('on', null, [left, operator, right]).toString(); - /// Inner join. [table] is the joined collection/table ID. + /// Inner join. [table] is the joined collection/table ID; without an alias the join is named after it. static String join( String table, Object leftOrAliasOrOn, [ @@ -420,7 +420,7 @@ class Query { ]) => Query._createJoin('join', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Left join. [table] is the joined collection/table ID. + /// Left join. [table] is the joined collection/table ID; without an alias the join is named after it. static String leftJoin( String table, Object leftOrAliasOrOn, [ @@ -430,7 +430,7 @@ class Query { ]) => Query._createJoin('leftJoin', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Right join. [table] is the joined collection/table ID. + /// Right join. [table] is the joined collection/table ID; without an alias the join is named after it. static String rightJoin( String table, Object leftOrAliasOrOn, [ @@ -440,7 +440,7 @@ class Query { ]) => Query._createJoin('rightJoin', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Full outer join. [table] is the joined collection/table ID. + /// Full outer join. [table] is the joined collection/table ID; without an alias the join is named after it. static String fullOuterJoin( String table, Object leftOrAliasOrOn, [ @@ -450,7 +450,7 @@ class Query { ]) => Query._createJoin('fullOuterJoin', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Cross join. [table] is the joined collection/table ID. + /// Cross join. [table] is the joined collection/table ID; without an alias the join is named after it. static String crossJoin(String table, [String alias = '']) => Query._('crossJoin', table, alias.isNotEmpty ? alias : null).toString(); diff --git a/templates/dart/test/query_test.dart.twig b/templates/dart/test/query_test.dart.twig index 7316c72ec5..18e79a654c 100644 --- a/templates/dart/test/query_test.dart.twig +++ b/templates/dart/test/query_test.dart.twig @@ -515,6 +515,23 @@ void main() { expect(query['values'], ['ord']); }); + test('returns nested join without alias, leaving the alias to the server', + () { + final query = + jsonDecode(Query.join('orders', [Query.on('\$id', 'customerId')])); + expect(query['method'], 'join'); + expect(query['attribute'], 'orders'); + expect(query['values'].length, 1); + expect(query['values'][0]['method'], 'on'); + }); + + test('returns crossJoin without alias, leaving the alias to the server', () { + final query = jsonDecode(Query.crossJoin('orders')); + expect(query['method'], 'crossJoin'); + expect(query['attribute'], 'orders'); + expect(query['values'] ?? [], isEmpty); + }); + test('returns vectorDot', () { final query = jsonDecode(Query.vectorDot('embedding', [0.1, 0.2])); expect(query['method'], 'vectorDot'); diff --git a/templates/go/query_test.go.twig b/templates/go/query_test.go.twig index bcd69f54c4..642bed1ae5 100644 --- a/templates/go/query_test.go.twig +++ b/templates/go/query_test.go.twig @@ -403,6 +403,38 @@ func TestCrossJoin(t *testing.T) { } } +func TestNestedJoinWithoutAliasLeavesTheAliasToTheServer(t *testing.T) { + q := JoinOn("orders", "", []string{On("$id", "customerId", "")}) + var data map[string]interface{} + if err := json.Unmarshal([]byte(q), &data); err != nil { + t.Fatal(err) + } + if data["method"] != "join" { + t.Errorf("Expected join, got %v", data["method"]) + } + values := data["values"].([]interface{}) + if len(values) != 1 { + t.Fatalf("Expected only the ON condition, got %v", values) + } + if values[0].(map[string]interface{})["method"] != "on" { + t.Errorf("Expected on, got %v", values[0]) + } +} + +func TestCrossJoinWithoutAliasLeavesTheAliasToTheServer(t *testing.T) { + q := CrossJoin("orders", "") + var data map[string]interface{} + if err := json.Unmarshal([]byte(q), &data); err != nil { + t.Fatal(err) + } + if data["method"] != "crossJoin" { + t.Errorf("Expected crossJoin, got %v", data["method"]) + } + if values, ok := data["values"].([]interface{}); ok && len(values) != 0 { + t.Errorf("Expected no alias, got %v", values) + } +} + func TestVectorDot(t *testing.T) { q := VectorDot("embedding", []float64{0.1, 0.2}) 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 dbb3e54142..832626dfe9 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig @@ -284,7 +284,7 @@ class Query( fun max(attribute: String, alias: String = "") = Query("max", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** - * Standard deviation of [attribute], optionally aliased. + * Population standard deviation of [attribute] (the same as stddevPop), optionally aliased. * * @param attribute The attribute to inspect. * @param alias Optional result alias. @@ -311,7 +311,7 @@ class Query( fun stddevSamp(attribute: String, alias: String = "") = Query("stddevSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** - * Variance of [attribute], optionally aliased. + * Population variance of [attribute] (the same as varPop), optionally aliased. * * @param attribute The attribute to inspect. * @param alias Optional result alias. @@ -398,19 +398,19 @@ class Query( fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() /** - * Inner join. [table] is the joined collection/table ID. + * Inner join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) /** - * Inner join with nested ON queries and no alias. + * Inner join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -429,19 +429,19 @@ class Query( fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) /** - * Left join. [table] is the joined collection/table ID. + * Left join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) /** - * Left join with nested ON queries and no alias. + * Left join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -460,19 +460,19 @@ class Query( fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) /** - * Right join. [table] is the joined collection/table ID. + * Right join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) /** - * Right join with nested ON queries and no alias. + * Right join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -491,19 +491,19 @@ class Query( fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) /** - * Full outer join. [table] is the joined collection/table ID. + * Full outer join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) /** - * Full outer join with nested ON queries and no alias. + * Full outer join with nested ON queries, named after the table ID. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -522,10 +522,10 @@ class Query( fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) /** - * Cross join. [table] is the joined collection/table ID. + * Cross join. [table] is the joined collection/table ID; without an alias the join is named after it. * * @param table Joined table ID. - * @param alias Optional table alias. + * @param alias Optional table alias. Defaults to the table ID. * @returns The query string. */ fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() 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 1054591bf5..5551961a75 100644 --- a/templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig +++ b/templates/kotlin/src/test/kotlin/io/appwrite/QueryTest.kt.twig @@ -2,6 +2,7 @@ package {{ sdk.namespace | caseDot }} import com.google.gson.JsonParser import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test class QueryTest { @@ -105,6 +106,25 @@ class QueryTest { assertEquals("crossJoin", method(Query.crossJoin("orders", "ord"))) } + @Test + fun nestedJoinWithoutAliasLeavesTheAliasToTheServer() { + val json = JsonParser.parseString(Query.join("orders", listOf(Query.on("\$id", "customerId")))).asJsonObject + assertEquals("join", json.get("method").asString) + assertEquals("orders", json.get("attribute").asString) + val values = json.get("values").asJsonArray + assertEquals(1, values.size()) + assertEquals("on", values[0].asJsonObject.get("method").asString) + } + + @Test + fun crossJoinWithoutAliasLeavesTheAliasToTheServer() { + val json = JsonParser.parseString(Query.crossJoin("orders")).asJsonObject + assertEquals("crossJoin", json.get("method").asString) + assertEquals("orders", json.get("attribute").asString) + val values = json.get("values") + assertTrue(values == null || values.isJsonNull || values.asJsonArray.size() == 0) + } + @Test fun on() { assertEquals("on", method(Query.on("\$id", "customerId"))) diff --git a/templates/node/test/query.test.js.twig b/templates/node/test/query.test.js.twig index 1083ecd70d..d84064f817 100644 --- a/templates/node/test/query.test.js.twig +++ b/templates/node/test/query.test.js.twig @@ -376,6 +376,23 @@ describe('Query', () => { values: ['ord'], })); + test('nested join without alias leaves the alias to the server', () => { + const query = JSON.parse( + Query.join('orders', [Query.on('$id', 'customerId')]), + ); + expect(query.method).toEqual('join'); + expect(query.attribute).toEqual('orders'); + expect(query.values).toHaveLength(1); + expect(query.values[0].method).toEqual('on'); + }); + + test('crossJoin without alias leaves the alias to the server', () => { + const query = JSON.parse(Query.crossJoin('orders')); + expect(query.method).toEqual('crossJoin'); + expect(query.attribute).toEqual('orders'); + expect(query.values ?? []).toEqual([]); + }); + test('vectorDot', () => expectQuery(Query.vectorDot('embedding', [0.1, 0.2])).toEqual({ method: 'vectorDot', diff --git a/templates/php/src/Query.php.twig b/templates/php/src/Query.php.twig index 9911f7c326..8ec0d9b445 100644 --- a/templates/php/src/Query.php.twig +++ b/templates/php/src/Query.php.twig @@ -560,7 +560,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Standard deviation of $attribute, optionally aliased. + * Population standard deviation of $attribute (the same as stddevPop), optionally aliased. */ public static function stddev(string $attribute, string $alias = ''): string { @@ -584,7 +584,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Variance of $attribute, optionally aliased. + * Population variance of $attribute (the same as varPop), optionally aliased. */ public static function variance(string $attribute, string $alias = ''): string { @@ -674,7 +674,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Inner join. $table is the joined collection/table ID. + * Inner join. $table is the joined collection/table ID; without an alias the join is named after it. */ public static function join(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -682,7 +682,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Left join. $table is the joined collection/table ID. + * Left join. $table is the joined collection/table ID; without an alias the join is named after it. */ public static function leftJoin(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -690,7 +690,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Right join. $table is the joined collection/table ID. + * Right join. $table is the joined collection/table ID; without an alias the join is named after it. */ public static function rightJoin(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -698,7 +698,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Full outer join. $table is the joined collection/table ID. + * Full outer join. $table is the joined collection/table ID; without an alias the join is named after it. */ public static function fullOuterJoin(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -706,7 +706,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Cross join. $table is the joined collection/table ID. + * Cross join. $table is the joined collection/table ID; without an alias the join is named after it. */ public static function crossJoin(string $table, string $alias = ''): string { diff --git a/templates/php/tests/QueryTest.php.twig b/templates/php/tests/QueryTest.php.twig index 60fdd5554a..dae6ef1b68 100644 --- a/templates/php/tests/QueryTest.php.twig +++ b/templates/php/tests/QueryTest.php.twig @@ -540,6 +540,23 @@ final class QueryTest extends TestCase $this->assertSame(['ord'], $query['values']); } + public function testNestedJoinWithoutAliasLeavesTheAliasToTheServer(): void + { + $query = json_decode(Query::join('orders', [Query::on('$id', 'customerId')]), true); + $this->assertSame('join', $query['method']); + $this->assertSame('orders', $query['attribute']); + $this->assertCount(1, $query['values']); + $this->assertSame('on', $query['values'][0]['method']); + } + + public function testCrossJoinWithoutAliasLeavesTheAliasToTheServer(): void + { + $query = json_decode(Query::crossJoin('orders'), true); + $this->assertSame('crossJoin', $query['method']); + $this->assertSame('orders', $query['attribute']); + $this->assertSame([], $query['values'] ?? []); + } + public function testNotCovers(): void { $query = json_decode(Query::notCovers('location', [1, 2]), true); diff --git a/templates/python/test/test_query.py.twig b/templates/python/test/test_query.py.twig index a7965f75a5..663ccbe1b9 100644 --- a/templates/python/test/test_query.py.twig +++ b/templates/python/test/test_query.py.twig @@ -267,6 +267,19 @@ class TestQueryMethods(unittest.TestCase): parsed(Query.cross_join('orders', 'ord')), {'method': 'crossJoin', 'attribute': 'orders', 'values': ['ord']} ) + def test_nested_join_without_alias_leaves_the_alias_to_the_server(self): + query = json.loads(Query.join('orders', [Query.on('$id', 'customerId')])) + self.assertEqual(query['method'], 'join') + self.assertEqual(query['attribute'], 'orders') + self.assertEqual(len(query['values']), 1) + self.assertEqual(query['values'][0]['method'], 'on') + + def test_cross_join_without_alias_leaves_the_alias_to_the_server(self): + query = json.loads(Query.cross_join('orders')) + self.assertEqual(query['method'], 'crossJoin') + self.assertEqual(query['attribute'], 'orders') + self.assertEqual(query.get('values') or [], []) + def test_not_covers(self): self.assertEqual( parsed(Query.not_covers('location', [1, 2])), diff --git a/templates/react-native/src/query.ts.twig b/templates/react-native/src/query.ts.twig index 9c614c7518..d885271275 100644 --- a/templates/react-native/src/query.ts.twig +++ b/templates/react-native/src/query.ts.twig @@ -652,7 +652,7 @@ export class Query { new Query('on', undefined, [left, operator, right]).toString(); /** - * Inner join. `table` is the joined collection/table ID. + * Inner join. `table` is the joined collection/table ID; without an alias the join is named after it. */ static join = ( table: string, diff --git a/templates/swift/Tests/Tests.swift.twig b/templates/swift/Tests/Tests.swift.twig index 267523b7c8..42a12562f0 100644 --- a/templates/swift/Tests/Tests.swift.twig +++ b/templates/swift/Tests/Tests.swift.twig @@ -84,6 +84,24 @@ class Tests: XCTestCase { XCTAssertEqual(method(Query.crossJoin("orders", alias: "ord")), "crossJoin") } + func testNestedJoinWithoutAliasLeavesTheAliasToTheServer() { + let raw = Query.join("orders", on: [Query.on("$id", "customerId")]) + let object = try! JSONSerialization.jsonObject(with: Data(raw.utf8)) as! [String: Any] + XCTAssertEqual(object["method"] as? String, "join") + XCTAssertEqual(object["attribute"] as? String, "orders") + let values = object["values"] as! [Any] + XCTAssertEqual(values.count, 1) + XCTAssertEqual((values[0] as! [String: Any])["method"] as? String, "on") + } + + func testCrossJoinWithoutAliasLeavesTheAliasToTheServer() { + let raw = Query.crossJoin("orders") + let object = try! JSONSerialization.jsonObject(with: Data(raw.utf8)) as! [String: Any] + XCTAssertEqual(object["method"] as? String, "crossJoin") + XCTAssertEqual(object["attribute"] as? String, "orders") + XCTAssertEqual((object["values"] as? [Any])?.count ?? 0, 0) + } + func testOn() { XCTAssertEqual(method(Query.on("$id", "customerId")), "on") } diff --git a/templates/web/src/query.ts.twig b/templates/web/src/query.ts.twig index ca0b350372..43705c75df 100644 --- a/templates/web/src/query.ts.twig +++ b/templates/web/src/query.ts.twig @@ -726,7 +726,7 @@ export class Query { alias !== '' ? alias : undefined, ).toString(); - /** Standard deviation of `attribute`, optionally aliased. */ + /** Population standard deviation of `attribute` (the same as stddevPop), optionally aliased. */ static stddev = (attribute: string, alias: string = ''): string => new Query( 'stddev', @@ -750,7 +750,7 @@ export class Query { alias !== '' ? alias : undefined, ).toString(); - /** Variance of `attribute`, optionally aliased. */ + /** Population variance of `attribute` (the same as varPop), optionally aliased. */ static variance = (attribute: string, alias: string = ''): string => new Query( 'variance', @@ -824,7 +824,7 @@ export class Query { new Query('on', undefined, [left, operator, right]).toString(); /** - * Inner join. `table` is the joined collection/table ID. + * Inner join. `table` is the joined collection/table ID; without an alias the join is named after it. */ static join = ( table: string, @@ -842,7 +842,7 @@ export class Query { alias, ); - /** Left join. `table` is the joined collection/table ID. */ + /** Left join. `table` is the joined collection/table ID; without an alias the join is named after it. */ static leftJoin = ( table: string, leftOrAliasOrOn: string | string[], @@ -859,7 +859,7 @@ export class Query { alias, ); - /** Right join. `table` is the joined collection/table ID. */ + /** Right join. `table` is the joined collection/table ID; without an alias the join is named after it. */ static rightJoin = ( table: string, leftOrAliasOrOn: string | string[], @@ -876,7 +876,7 @@ export class Query { alias, ); - /** Full outer join. `table` is the joined collection/table ID. */ + /** Full outer join. `table` is the joined collection/table ID; without an alias the join is named after it. */ static fullOuterJoin = ( table: string, leftOrAliasOrOn: string | string[], @@ -893,7 +893,7 @@ export class Query { alias, ); - /** Cross join. `table` is the joined collection/table ID. */ + /** Cross join. `table` is the joined collection/table ID; without an alias the join is named after it. */ static crossJoin = (table: string, alias: string = ''): string => new Query( 'crossJoin', From 75a9592df5b085c81bf7ee73f0b419663aea3b26 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 17 Oct 2026 15:09:51 +1300 Subject: [PATCH 2/2] docs(query): say the join alias default only applies to valid aliases A table ID that is not a valid alias is rejected rather than defaulted, so the docs no longer read as an unconditional default. Co-Authored-By: Claude Opus 5.5 --- .../src/main/java/io/package/Query.kt.twig | 28 +++++++++---------- templates/dart/lib/query.dart.twig | 10 +++---- .../src/main/kotlin/io/appwrite/Query.kt.twig | 28 +++++++++---------- templates/php/src/Query.php.twig | 10 +++---- templates/react-native/src/query.ts.twig | 2 +- templates/web/src/query.ts.twig | 10 +++---- 6 files changed, 44 insertions(+), 44 deletions(-) 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 d781241900..f080250e88 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 @@ -698,19 +698,19 @@ class Query( fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() /** - * Inner join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Inner join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) /** - * Inner join with nested ON queries, named after the table ID. + * Inner join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -729,19 +729,19 @@ class Query( fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) /** - * Left join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Left join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) /** - * Left join with nested ON queries, named after the table ID. + * Left join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -760,19 +760,19 @@ class Query( fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) /** - * Right join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Right join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) /** - * Right join with nested ON queries, named after the table ID. + * Right join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -791,19 +791,19 @@ class Query( fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) /** - * Full outer join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Full outer join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) /** - * Full outer join with nested ON queries, named after the table ID. + * Full outer join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -822,10 +822,10 @@ class Query( fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) /** - * Cross join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Cross join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() diff --git a/templates/dart/lib/query.dart.twig b/templates/dart/lib/query.dart.twig index c2b25e1fe8..a4deb17f73 100644 --- a/templates/dart/lib/query.dart.twig +++ b/templates/dart/lib/query.dart.twig @@ -410,7 +410,7 @@ class Query { static String on(String left, String right, [String operator = '=']) => Query._('on', null, [left, operator, right]).toString(); - /// Inner join. [table] is the joined collection/table ID; without an alias the join is named after it. + /// Inner join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. static String join( String table, Object leftOrAliasOrOn, [ @@ -420,7 +420,7 @@ class Query { ]) => Query._createJoin('join', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Left join. [table] is the joined collection/table ID; without an alias the join is named after it. + /// Left join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. static String leftJoin( String table, Object leftOrAliasOrOn, [ @@ -430,7 +430,7 @@ class Query { ]) => Query._createJoin('leftJoin', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Right join. [table] is the joined collection/table ID; without an alias the join is named after it. + /// Right join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. static String rightJoin( String table, Object leftOrAliasOrOn, [ @@ -440,7 +440,7 @@ class Query { ]) => Query._createJoin('rightJoin', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Full outer join. [table] is the joined collection/table ID; without an alias the join is named after it. + /// Full outer join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. static String fullOuterJoin( String table, Object leftOrAliasOrOn, [ @@ -450,7 +450,7 @@ class Query { ]) => Query._createJoin('fullOuterJoin', table, leftOrAliasOrOn, rightOrOn, operator, alias); - /// Cross join. [table] is the joined collection/table ID; without an alias the join is named after it. + /// Cross join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. static String crossJoin(String table, [String alias = '']) => Query._('crossJoin', table, alias.isNotEmpty ? alias : null).toString(); 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 832626dfe9..e9d9753214 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig @@ -398,19 +398,19 @@ class Query( fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() /** - * Inner join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Inner join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) /** - * Inner join with nested ON queries, named after the table ID. + * Inner join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -429,19 +429,19 @@ class Query( fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) /** - * Left join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Left join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) /** - * Left join with nested ON queries, named after the table ID. + * Left join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -460,19 +460,19 @@ class Query( fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) /** - * Right join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Right join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) /** - * Right join with nested ON queries, named after the table ID. + * Right join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -491,19 +491,19 @@ class Query( fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) /** - * Full outer join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Full outer join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. * @param left Left-hand join column. * @param right Right-hand join column. * @param operator Comparison operator. Defaults to `=`. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) /** - * Full outer join with nested ON queries, named after the table ID. + * Full outer join with nested ON queries, named after the table ID when that is a valid alias. * * @param table Joined table ID. * @param on Nested ON queries, including [on]. @@ -522,10 +522,10 @@ class Query( fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) /** - * Cross join. [table] is the joined collection/table ID; without an alias the join is named after it. + * Cross join. [table] is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. * * @param table Joined table ID. - * @param alias Optional table alias. Defaults to the table ID. + * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() diff --git a/templates/php/src/Query.php.twig b/templates/php/src/Query.php.twig index 8ec0d9b445..c6d9a7d761 100644 --- a/templates/php/src/Query.php.twig +++ b/templates/php/src/Query.php.twig @@ -674,7 +674,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Inner join. $table is the joined collection/table ID; without an alias the join is named after it. + * Inner join. $table is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ public static function join(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -682,7 +682,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Left join. $table is the joined collection/table ID; without an alias the join is named after it. + * Left join. $table is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ public static function leftJoin(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -690,7 +690,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Right join. $table is the joined collection/table ID; without an alias the join is named after it. + * Right join. $table is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ public static function rightJoin(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -698,7 +698,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Full outer join. $table is the joined collection/table ID; without an alias the join is named after it. + * Full outer join. $table is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ public static function fullOuterJoin(string $table, string|array $leftOrAliasOrOn, string|array $rightOrOn = '', string $operator = '=', string $alias = ''): string { @@ -706,7 +706,7 @@ class Query implements \JsonSerializable, \Stringable } /** - * Cross join. $table is the joined collection/table ID; without an alias the join is named after it. + * Cross join. $table is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ public static function crossJoin(string $table, string $alias = ''): string { diff --git a/templates/react-native/src/query.ts.twig b/templates/react-native/src/query.ts.twig index d885271275..7b5c091337 100644 --- a/templates/react-native/src/query.ts.twig +++ b/templates/react-native/src/query.ts.twig @@ -652,7 +652,7 @@ export class Query { new Query('on', undefined, [left, operator, right]).toString(); /** - * Inner join. `table` is the joined collection/table ID; without an alias the join is named after it. + * Inner join. `table` is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ static join = ( table: string, diff --git a/templates/web/src/query.ts.twig b/templates/web/src/query.ts.twig index 43705c75df..f25c491541 100644 --- a/templates/web/src/query.ts.twig +++ b/templates/web/src/query.ts.twig @@ -824,7 +824,7 @@ export class Query { new Query('on', undefined, [left, operator, right]).toString(); /** - * Inner join. `table` is the joined collection/table ID; without an alias the join is named after it. + * Inner join. `table` is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ static join = ( table: string, @@ -842,7 +842,7 @@ export class Query { alias, ); - /** Left join. `table` is the joined collection/table ID; without an alias the join is named after it. */ + /** Left join. `table` is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ static leftJoin = ( table: string, leftOrAliasOrOn: string | string[], @@ -859,7 +859,7 @@ export class Query { alias, ); - /** Right join. `table` is the joined collection/table ID; without an alias the join is named after it. */ + /** Right join. `table` is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ static rightJoin = ( table: string, leftOrAliasOrOn: string | string[], @@ -876,7 +876,7 @@ export class Query { alias, ); - /** Full outer join. `table` is the joined collection/table ID; without an alias the join is named after it. */ + /** Full outer join. `table` is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ static fullOuterJoin = ( table: string, leftOrAliasOrOn: string | string[], @@ -893,7 +893,7 @@ export class Query { alias, ); - /** Cross join. `table` is the joined collection/table ID; without an alias the join is named after it. */ + /** Cross join. `table` is the joined collection/table ID; without an alias the server names the join after it, if the ID is a valid alias, and otherwise rejects the query. */ static crossJoin = (table: string, alias: string = ''): string => new Query( 'crossJoin',