Skip to content

Commit

Permalink
Implement GraphQL Query Complexity Analysis (#1965)
Browse files Browse the repository at this point in the history
* Add java error logs to gitignore

* [Automated] Update the native jar versions

* [Automated] Update the native jar versions

* [Automated] Update the native jar versions

* [Automated] Update the native jar versions

* [Automated] Update the native jar versions

* Query complexity analysis initial implementation

* [Automated] Update the native jar versions

* Fix service annotation failing when module prefix is available

* Complete the query complexity analysis feature

* Fix spotbugs failure

* Apply suggestions from code review

Co-authored-by: Ayesh Almeida <77491511+ayeshLK@users.noreply.github.com>

* Fix the formatting

* Apply suggestions from code review

Co-authored-by: Ayesh Almeida <77491511+ayeshLK@users.noreply.github.com>

* [Automated] Update the native jar versions

* [Automated] Update the native jar versions

* Fix annotations not populating issue

* Apply suggestions from code review

Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com>

* Fix typo in the function name

* Update the Ballerina distribution version

* [Automated] Update the native jar versions

* Fix introspection query complexity validation

* Fix introspection query complexity validation and update spec

---------

Co-authored-by: Ayesh Almeida <77491511+ayeshLK@users.noreply.github.com>
Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 26, 2024
1 parent 1f3d6a5 commit 8112144
Show file tree
Hide file tree
Showing 49 changed files with 1,901 additions and 223 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ velocity.log*
graphql-ballerina/modules/graphql/
examples/**/Dependencies.toml
load-tests/**/Dependencies.toml

# Java Logs
java_pid*.hprof
2 changes: 1 addition & 1 deletion ballerina-tests/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "graphql_tests"
version = "1.13.1"
version = "1.14.0"

[build-options]
observabilityIncluded = true
12 changes: 6 additions & 6 deletions ballerina-tests/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.9.0"
distribution-version = "2201.10.0-20240702-135600-18965cba"

[[package]]
org = "ballerina"
Expand Down Expand Up @@ -44,7 +44,7 @@ modules = [
[[package]]
org = "ballerina"
name = "crypto"
version = "2.7.0"
version = "2.7.2"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "time"}
Expand All @@ -67,7 +67,7 @@ modules = [
[[package]]
org = "ballerina"
name = "graphql"
version = "1.13.1"
version = "1.14.0"
dependencies = [
{org = "ballerina", name = "auth"},
{org = "ballerina", name = "cache"},
Expand Down Expand Up @@ -99,7 +99,7 @@ modules = [
[[package]]
org = "ballerina"
name = "graphql_tests"
version = "1.13.1"
version = "1.14.0"
dependencies = [
{org = "ballerina", name = "constraint"},
{org = "ballerina", name = "file"},
Expand All @@ -123,7 +123,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http"
version = "2.11.0"
version = "2.11.3"
dependencies = [
{org = "ballerina", name = "auth"},
{org = "ballerina", name = "cache"},
Expand Down Expand Up @@ -172,7 +172,7 @@ version = "0.0.0"
[[package]]
org = "ballerina"
name = "jwt"
version = "2.11.0"
version = "2.12.1"
dependencies = [
{org = "ballerina", name = "cache"},
{org = "ballerina", name = "crypto"},
Expand Down
199 changes: 199 additions & 0 deletions ballerina-tests/objects.bal
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// specific language governing permissions and limitations
// under the License.

import ballerina/graphql;

public type Character distinct service object {
isolated resource function get name() returns string;
};
Expand Down Expand Up @@ -260,3 +262,200 @@ Animalia[] animals = [
public isolated service class ReviewData {
isolated resource function get id() returns string => "123";
}

public type Device distinct service object {
isolated resource function get id() returns @graphql:ID int;

@graphql:ResourceConfig {
complexity: 1
}
isolated resource function get brand() returns string;

isolated resource function get model() returns string;

@graphql:ResourceConfig {
complexity: 4
}
isolated resource function get price() returns float;
};

public isolated distinct service class Phone {
*Device;

private final int id;
private final string brand;
private final string model;
private final float price;
private final OS os;
private final Device[] connectedDevices;

isolated function init(int id, string brand, string model, float price, OS os) {
self.id = id;
self.brand = brand;
self.model = model;
self.price = price;
self.os = os;
}

isolated resource function get id() returns @graphql:ID int => self.id;

isolated resource function get brand() returns string => self.brand;

isolated resource function get model() returns string => self.model;

isolated resource function get price() returns float => self.price;

isolated resource function get os() returns OS => self.os;
}

public isolated distinct service class Laptop {
*Device;

private final int id;
private final string brand;
private final string model;
private final float price;
private final string processor;
private final int ram;

isolated function init(int id, string brand, string model, float price, string processor, int ram) {
self.id = id;
self.brand = brand;
self.model = model;
self.price = price;
self.processor = processor;
self.ram = ram;
}

isolated resource function get id() returns @graphql:ID int => self.id;

isolated resource function get brand() returns string => self.brand;

isolated resource function get model() returns string => self.model;

isolated resource function get price() returns float => self.price;

isolated resource function get processor() returns string => self.processor;

isolated resource function get ram() returns int => self.ram;
}

public isolated distinct service class Tablet {
*Device;

private final int id;
private final string brand;
private final string model;
private final float price;
private final boolean hasCellular;

isolated function init(int id, string brand, string model, float price, boolean hasCellular) {
self.id = id;
self.brand = brand;
self.model = model;
self.price = price;
self.hasCellular = hasCellular;
}

isolated resource function get id() returns @graphql:ID int => self.id;

isolated resource function get brand() returns string => self.brand;

isolated resource function get model() returns string => self.model;

isolated resource function get price() returns float => self.price;

isolated resource function get hasCellular() returns boolean => self.hasCellular;
}

enum OS {
iOS,
Android,
Windows
}

type Mobile Phone|Tablet;

type RatingInput readonly & record {|
string title;
int stars;
string description;
int authorId;
|};

type RatingData readonly & record {|
readonly int id;
string title;
int stars;
string description;
int authorId;
|};

service class Rating {
private final RatingData data;

isolated function init(RatingData data) {
self.data = data;
}

isolated resource function get id() returns @graphql:ID int => self.data.id;

@graphql:ResourceConfig {
complexity: 1
}
isolated resource function get title() returns string => self.data.title;

@graphql:ResourceConfig {
complexity: 1
}
isolated resource function get stars() returns int => self.data.stars;

isolated resource function get description() returns string => self.data.description;

@graphql:ResourceConfig {
complexity: 10
}
isolated resource function get author() returns DeviceUserProfile|error {
lock {
if profileTable.hasKey(self.data.authorId) {
return new DeviceUserProfile(profileTable.get(self.data.authorId));
}
}
return error("Author not found");
}
}

type DeviceUserProfileData readonly & record {|
readonly int id;
string name;
int age;
|};

service class DeviceUserProfile {
private final DeviceUserProfileData data;

isolated function init(DeviceUserProfileData data) {
self.data = data;
}

isolated resource function get id() returns @graphql:ID int => self.data.id;

isolated resource function get name() returns string => self.data.name;

isolated resource function get age() returns int => self.data.age;
}

isolated table<RatingData> key(id) ratingTable = table [
{id: 1, title: "Good", stars: 4, description: "Good product", authorId: 1},
{id: 2, title: "Bad", stars: 2, description: "Bad product", authorId: 2},
{id: 3, title: "Excellent", stars: 5, description: "Excellent product", authorId: 3},
{id: 4, title: "Poor", stars: 1, description: "Poor product", authorId: 4}
];

isolated table<DeviceUserProfileData> key(id) profileTable = table [
{id: 1, name: "Alice", age: 25},
{id: 2, name: "Bob", age: 30},
{id: 3, name: "Charlie", age: 35},
{id: 4, name: "David", age: 40}
];

56 changes: 55 additions & 1 deletion ballerina-tests/services.bal
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
// under the License.

import ballerina/graphql as gql;
import ballerina/graphql;

listener gql:Listener graphqlListener = new(9098);
listener gql:Listener graphqlListener = new (9098);

service /interfaces on graphqlListener {
isolated resource function get character(int id) returns Character {
Expand All @@ -37,3 +38,56 @@ service /interfaces_implementing_interface on graphqlListener {
return id <= animals.length() && id > 0 ? animals[id - 1] : ();
}
}

@gql:ServiceConfig {
queryComplexityConfig: {
maxComplexity: 20,
defaultFieldComplexity: 2
}
}
service /complexity on graphqlListener {
resource function get greeting() returns string {
return "Hello";
}

@gql:ResourceConfig {
complexity: 10
}
resource function get device(int id) returns Device {
if id < 10 {
return new Phone(id, "Apple", "iPhone 15 Pro", 1199.00, "iOS");
}

if id < 20 {
return new Laptop(id, "Apple", "MacBook Pro", 2399.00, "M1", 32);
}

return new Tablet(id, "Samsung", "Galaxy Tab S7", 649.99, true);
}

@gql:ResourceConfig {
complexity: 5
}
resource function get mobile(int id) returns Mobile {
if id < 10 {
return new Phone(id, "Apple", "iPhone 15 Pro", 1199.00, "iOS");
}

return new Tablet(id, "Samsung", "Galaxy Tab S7", 649.99, true);
}

@graphql:ResourceConfig {
complexity: 15
}
remote function addReview(RatingInput input) returns Rating {
lock {
int id = ratingTable.length();
RatingData rating = {
id,
...input
};
ratingTable.put(rating);
return new (rating);
}
}
}
Loading

0 comments on commit 8112144

Please sign in to comment.