Gelly Reference 

Built-in data types 

Gelly supports literals for a variety of built-in data types that you can use in your expressions. The syntax is very similar to JavaScript, so you can usually use the same syntax to declare literals as you might in JS within Gelly snippets.

Integers and Numbers are written out as the digits of the number:

Number literals
gelly
query {
anInteger: 5
aDecimal: 10.5
}

Booleans are written as true or false:

Boolean literals
gelly
query {
yes: true
no: false
}

Strings are written with the characters between double quotes, and using \" to escape quote characters within the string:

String literals
gelly
query {
aString: "foo"
withAQuoteInIt: "when she said \"yes\", it was incredible!"
}

DateTimes are written using the timestamp function which accepts a timestamp string as input. The input string is formatted according to Postgres' timestamp literal syntax, which is documented in the Postgres docs. If not specified, times are assumed to be in UTC.

Date literals
gelly
query {
aTimestamp: timestamp("2021-05-01 15:00:00")
anISO8601Timestamp: timestamp("2021-05-01T15:00:00Z")
}

Dates are written using the date function which accepts a date string as input. The input string is formatted according to Postgres' date literal syntax, which is documented in the Postgres docs.

Date literals
gelly
query {
anISO8601Date: date("2021-05-01")
aDate: date("January 8, 1999")
}

Intervals represent durations and can be created using the interval function that allows a verbose way of describing the value, e.g. 3 weeks 7 hours or a more concise (SQL standard format YY-MM DD HH:MM:SS) 21 7: or even a few variations of the ISO standard P3WT7H. More details on the supported syntaxes can be found in the Postgres docs.

Intervals can also result from subtracting Dates or DateTimes. Intervals can be added or subtracted from Dates and DateTimes as well. For more details on math with dates see Postgres docs.

Note that interval values are not automatically normalized, e.g. "60 hours" doesn't immediately become "2 days 12 hours". Consequently retrieving parts of an interval may sometime seem counterintuitive. However Gelly does normalize the interval value when it is returned as a result.

Interval literals
gelly
query {
interval1: interval("3 days 4 hours")
interval2: interval("P3YT2H")
interval3: date("2021-05-01") - timestamp("2021-05-01 15:00:00")
}

nulls are written as null. There's no undefined in Gelly.

Arrays are written between square brackets with commas (,) separating the elements.

Array literals
gelly
query {
someNumbers: [1, 2, 3]
someStrings: ["foo", "bar", "baz"]
myStuff: [1, "foo", true, null]
}

Objects are written between curly brackets with the keys on the left-hand side of a colon (:) and commas (,) separating the elements.

Object literals
gelly
query {
anObject: {foo: "bar", baz: true}
}

Built-in operators 

Gelly has several built-in operators for arithmetic, comparison, and boolean logic.

Boolean logic 

Prefix a boolean expression with an exclamation mark to inverse the boolean value.

Logical not
gelly
query {
!false # returns true
}

Test if two booleans are both true with &&, or if either boolean is true with ||

The number line
gelly
1query {
2 true && true # returns true
3 true && false # returns false
4 true || false # returns true
5 false || false # returns false
6}

Equality and inequality 

Test if two expressions are equal with two equal signs, like ==.

Expression Equality
gelly
1query {
2 1 == 1 # returns true
3 1 == 2 # returns false
4 true == true # returns true
5 true == false # returns false
6}

Test if two expressions are not equal with !=.

Expression Inequality
gelly
1query {
2 1 != 1 # returns false
3 1 != 2 # returns true
4 true != true # returns false
5 true != false # returns true
6}

Numeric ordering 

Test how two numbers relate to each other with >, >=, <, and <=.

The number line
gelly
query {
1 > 1 # returns false
1 >= 1 # returns true
10 > 1 # returns true
}

Arithmetic 

Add, subtract, multiply or divide numbers with +, -, *, or /:

Back to elementary school
gelly
query {
1 + 1 # returns 2
2 * 10 # returns 10
10 / 2 # returns 5
}

Division in Gelly returns decimal numbers with arbitrary precision. There is no integer division operator. This is different than most SQL engines, which do integer division by default, and ECMAScript, which uses IEEE-754 floating point numbers and thus has limited precision for mathematics.

Division
gelly
query {
10 / 3 # returns 3.3 repeating
}

Use the % operator for a modulus (integer division remainder).

Modulus
gelly
query {
10 % 3 # returns 1
}

Functions 

When working with functions, it is important to remember that argument keys are required. However, if a function accepts more than one argument, you may choose to omit the key for the first argument.

Boolean functions 

isNull(value: Any): Boolean! 

Returns true if the passed value is null, and false otherwise.

To check if something is null, you must use the isNull function, and not do an equality check like == null. This is because like SQL, null in Gelly is a special state of a data type that exists for every data type, and not a value itself. This is different than JavaScript or other statement-based languages where null == null, in Gelly, anything == null returns null itself. This is called three-valued logic and allows folks writing Gelly queries to not have to do constant null checks to make sure that a value is not null before manipulating or selecting it.

Null checks
gelly
query {
isNull(null) # returns true
isNull({apple: "red"}.apple) # returns false
isNull({apple: "red"}.orange) # returns true
}

String functions 

concat(string: [String!]!, delimiter: String): String! 

Returns a new string built by stringing each input string together, optionally with a delimiter.

Concatenate string literals together
gelly
query {
concat(["Hello", "", "world!"])
}
Concatenate fields from a selection together
gelly
query {
posts {
details: concat([title, " by ", author.name])
}
}
Concatenate array with a delimiter
gelly
query {
posts {
details: concat(tags, delimiter: ", ")
}
}

leftSlice(string: String!, length: Number): String! 

Returns a substring of the input string, starting from the first (leftmost) character position and including length characters after.

Take the left slice of a string
gelly
query {
leftSlice("foobar", length: 3) # returns "foo"
}

length(string: String!): Integer! 

Returns the number of characters in the passed-in string.

Get the length of a string
gelly
query {
length("foobar") # returns 6
}

lower(string: String!): String! 

Returns the lower-cased version of the input string.

Lowercase a string
gelly
query {
lower("FooBar") # returns "foobar"
}

rightSlice(string: String!, length: Number): String! 

Returns a substring of the input string, starting from the last (rightmost) character position and including a length number of previous characters.

Take the right slice of a string
gelly
query {
rightSlice("foobar", length: 3) # returns "bar"
}

slice(string: String!, start: Number = 0, length: Number): String! 

Returns a substring of the input string, starting from the start character position, or the first character if no start is passed, and going length characters further, or to the end of the string if length is not passed.

Slice a string from the start
gelly
query {
slice("foobar", length: 3) # returns "foo"
}

The start character position is inclusive, so start: 0 would include the first character in the string onwards, or start: 1 would skip the first character but include the second character onwards.

Slice a string in the middle
gelly
query {
slice("foobar", start: 2, length: 3) # returns "oba"
}

If the start argument is provided and the length argument isn't provided, the remainder of the string from the start position is returned.

Slice a string in the middle
gelly
query {
slice("foobar", start: 4) # returns "ar"
}

upper(string: String!): String! 

Returns the upper-cased version of the input string.

Uppercase a string
gelly
query {
upper("FooBar") # returns "FOOBAR"
}

startsWith(string: String!, prefix: String!): Boolean! 

Returns true if the string starts with the given prefix, and false otherwise. The prefix must be the same case as the input string.

String starts with
gelly
query {
startsWith("FooBar", prefix: "Foo") # returns true
startsWith("FooBar", prefix: "foo") # returns false
}

JSON functions 

contains(value: JSON!, item: (String | JSON)!): Boolean! 

Returns true if the given item is a subset of the given JSON value, and false otherwise. The value to be searched must be a JSON object or array, and the item can be a JSON object, array, string, or number. Corresponds to the @> operator in Postgres.

contains can be used to check if a JSON object contains a subset of another JSON object:

Matching objects
gelly
query {
contains({ foo: "bar", other: true }, item: { foo: "bar" }) # returns true, as the item is a subset of the value
}

contains can be used to check if an array contains an item:

Matching objects
gelly
query {
contains(["a", "b", "c"], item: "a") # returns true, as "a" is contained within the array
}

contains can be used to check if an object has a key:

Matching objects
gelly
query {
contains({foo: "1"}, item: "foo") # returns true, as the object has the foo key
contains({foo: "1"}, item: "bar") # returns false, as the object doesn't have the foo key
}

jsonPathMatch(value: JSON!, condition: String!): Boolean! 

Returns true if the JSON value matches the Postgres jsonpath filter expressions in condition as described in https://www.postgresql.org/docs/13/functions-json.html#FUNCTIONS-SQLJSON-FILTER-EX-TABLE. You can use $ to refer to the value and any valid jsonpath expression to navigate its structure as described in https://www.postgresql.org/docs/13/functions-json.html#FUNCTIONS-SQLJSON-PATH.

Matching jsonpath filter expression
gelly
1query {
2 jsonPathMatch(value: [1,2,3], condition: "$[0] == 2") # false, first element is 1
3 jsonPathMatch([1,2,3], condition: "$[*] == 2") # true, one of the elements is 2
4 jsonPathMatch([1,2,3], condition: "!($[*] == 5)") # true, none of the elements is 5
5 jsonPathMatch({a: 1}, condition: "$.a == 1") # true, property a has value 1
6 jsonPathMatch({a: [{b: 1}, {b: 2}]}, condition: "$.a[1].b == 2") # true
7 jsonPathMatch({a: [{b: 1}, {b: 2}]}, condition: "$.a[*].b == 2") # true
8}

Aggregation functions 

The where argument 

All aggregate functions in Gelly take a where argument which allows for filtering the set of records for passing through the function. The where argument doesn't filter the returned set of records, or affect other function calls, it only affects the aggregate function to which it is passed. where is a convenient way of quickly filtering records without having to filter the whole record set, but can be used interchangeably with the where relational command.

Using where with the count function allows counting records that match certain criteria.

Count the number of published posts
gelly
query {
posts {
count(id, where: isPublished)
}
}

This is equivalent to running count with a [where] relational command.

Count the number of published posts
gelly
1query {
2 posts {
3 count(id)
4 [where isPublished]
5 }
6}

Notably, the where argument can be used to issue multiple aggregate calls at once with different filter conditions without having to do many outer selection sets. The [where] relational command filters all records before passing them onto the functions, so all aggregate functions in a selection set are given the same, already filtered list of records to aggregate.

Aggregate posts with different filters
gelly
1query {
2 posts {
3 totalCount: count(id)
4 publishedCount: count(id, where: isPublished)
5 highScoreCount: count(id, where: score > 10)
6 bannedCount: count(id, where: author.banned)
7 }
8}

avg(number: Number!, where: Boolean): Number! 

Averages the given field number across the aggregated set, optionally filtered to only consider values that pass the where condition.

Average all post scores by author
gelly
1query {
2 posts {
3 avg(score)
4 [group by author.name]
5 }
6}

To do a filtered average easily, you can use the where argument to the avg function.

Average only published post scores
gelly
query {
posts {
avg(score, where: isPublished)
}
}

every(where: Boolean): Boolean! 

Returns true if all the passed values are true themselves, or false if any of the passed values are falsy.

Check if all posts have been published
gelly
query {
posts {
every(published)
}
}

To check if a subset of records match the passed conditions, use a where relational command.

Check if all published posts have a high score
gelly
1query {
2 posts {
3 every(score > 10)
4 [where published]
5 }
6}

any(where: Boolean): Boolean! 

Returns true if any of the passed values are true themselves, or false if all of the passed values are falsy.

Check if any posts have been published
gelly
query {
posts {
any(published)
}
}

To check if any of a subset of records match the passed conditions, use a where relational command.

Check if any published posts have a high score
gelly
1query {
2 posts {
3 any(score > 10)
4 [where published]
5 }
6}

count(value: Any, where: Boolean): Integer! 

Counts the number of non-null values across the aggregated set, optionally filtered to only consider values which pass the where condition.

Count the number of posts
gelly
query {
posts {
count(id)
}
}

To do a filtered count easily, you can use the where argument to the count function.

Count the number of published posts
gelly
query {
posts {
count(id, where: isPublished)
}
}

count is also subject to the relational commands for the query, so you can filter a count aggregate as well as other aggregates at the same time.

Get the average score and count of published posts
gelly
1query {
2 posts {
3 count(id)
4 avg(score)
5 [where published]
6 }
7}

max(number: Number!, where: Boolean): Number! 

Finds the maximum value of the number argument across the aggregated set, optionally filtered to only consider values which pass the where condition.

Find the maximum post scores by author
gelly
1query {
2 posts {
3 max(score)
4 [group by author.name]
5 }
6}

To find the maximum value in a subset of records easily, you can use the where argument to the max function.

Maximum published post score
gelly
query {
posts {
max(score, where: isPublished)
}
}

min(number: Number!, where: Boolean): Number! 

Finds the minimum value of the number argument across the aggregated set, optionally filtered to only consider values which pass the where condition.

Find the minimum post scores by author
gelly
1query {
2 posts {
3 min(score)
4 [group by author.name]
5 }
6}

To find the minimum value in a subset of records easily, you can use the where argument to the min function.

Minimum published post score
gelly
query {
posts {
min(score, where: isPublished)
}
}

sum(number: Number!, where: Boolean): Number! 

Adds the value of the number argument together across the whole aggregated set, optionally adding only values which pass the where condition.

Get the total votes cast by author
gelly
1query {
2 posts {
3 sum(voteCount)
4 [group by author.name]
5 }
6}

To sum only the value from a subset of records easily, you can use the where argument to the sum function.

Maximum published post score
gelly
query {
posts {
max(score, where: isPublished)
}
}

Time functions 

date(input: String!): Date! 

Create a Date object from an input string representing a date (without a time). Uses the Postgres date formatting syntax -- see more details in the Postgres docs.

Create a Date object
gelly
query {
date("2021-05-05")
}
Posts scheduled for publishing after a certain threshold
gelly
1query {
2 posts {
3 id
4 publishAt
5 [where publishAt > date("2021-05-05")]
6 }
7}

makeDate(year: Integer!, month: Integer, day: Integer): Date! 

Create a Date object from numbers representing the year, month and day. If not specified, both day and month default to 1. The numbers do not have to be literal, they can come from arbitrary expressions.

Create a Date object
gelly
query {
posts: {
startOfFollowingYear: makeDate(year: datePart("year", date: publishedAt))
}
}

datePart(part: String!, date: (DateTime | Date | Interval)!): Number! 

Retrieve a given component of a DateTime, Date or Interval object, like the year, week, or minutes. Uses the Postgres syntax for the date part, and returns it as a number. The valid parts are century, day, decade, dow (day of week), doy (day of year), epoch, hour, isodow, isoyear, microseconds, millennium, milliseconds, minute, month, quarter, second, week, year (Plural versions of these are also permitted as they suite the Interval type better). Only sensible combinations of types and part names will execute successfully.

See more details about date_part in the Postgres docs.

Get the year of a date
gelly
query {
datePart("year", date: date("2021-05-05"))
}
Day of week publishing schedule
gelly
1query {
2 posts {
3 id
4 publishDay: datePart("dow", publishAt)
5 }
6}

dateTrunc(part: String!, date: (DateTime | Date | Interval)!): (DateTime | Date | Interval)! 

Rounds down a DateTime, Date or Interval object to the closest date part. Uses the Postgres syntax for the date part. The valid parts are microseconds, milliseconds, second, minute, hour, day, week, month, quarter, year, decade, century, millennium (Plural versions of these are also permitted as they suite the Interval type better). Only sensible combinations of types and part names will execute successfully.

See more details about date_trunc in the Postgres docs.

Get the start of the current month
gelly
query {
dateTrunc("month", date: now())
}

interval(string: String!): Interval! 

Create an Interval object from an input string representing a specific duration of time. interval uses the Postgres interval input syntax -- see more details in the Postgres docs.

Create an Interval value
gelly
query {
short: interval("5 minutes")
long: interval("1 year")
longer: interval("1 year") + interval("6 months")
}
Posts published in the last 60 days
gelly
1query {
2 posts {
3 id
4 publishAt
5 [where publishAt > now() - interval("60 days")]
6 }
7}

makeInterval(years: Integer, months: Integer, weeks: Integer, days: Integer, hours: Integer, minutes: Integer, seconds: Number): Interval! 

Create an Interval object from number of years, months, weeks, days, hours, minutes, seconds. If not specified, all arguments default to 0. The seconds argument can be a decimal number representing fractional seconds. The numbers do not have to be literal, they can come from arbitrary expressions.

Create an Interval value
gelly
query {
short: makeInterval(minutes: 5)
pastTrial: now() - startedAt > makeInterval(days: trialDays)
}

now(): DateTime! 

Gets the current system time from the Gelly server.

Current Time
gelly
query {
now()
}
Posts scheduled for publishing in the future
gelly
1query {
2 posts {
3 id
4 publishAt
5 [where publishAt > now()]
6 }
7}
Posts scheduled for publishing in the future
gelly
1query {
2 posts {
3 id
4 publishAt
5 [where publishAt > now()]
6 }
7}

timestamp(input: String!): DateTime! 

Create a DateTime object from an input string representing a specific point in time. Uses the Postgres time formatting syntax -- see more details in the Postgres docs. If not specified, times are assumed to be in UTC.

Create a DateTime value
gelly
query {
timestamp("2021-05-05 10:10:00")
}
Posts scheduled for publishing after a certain threshold
gelly
1query {
2 posts {
3 id
4 publishAt
5 [where publishAt > timestamp("2021-05-01T15:00:00Z")]
6 }
7}

makeTimestamp(year: Integer!, month: Integer, day: Integer, hour: Integer, minute: Integer, second: Integer): DateTime! 

Create a DateTime object from numbers representing year, month, day, hour, minute, second. If not specified, month and day default to 1, the rest of the arguments default to 0. The seconds argument can be a decimal number representing fractional seconds. The numbers do not have to be literal, they can come from arbitrary expressions.

Create a DateTime value
gelly
query {
makeTimestamp(year: 2021, month: 5, day: 3)
}

Numeric functions 

abs(number: Number!): Integer! 

Gets the absolute value of a given input number.

Absolute value
gelly
query {
abs(-10) # returns 10
abs(10) # returns 10
}

ceil(number: Number!): Integer! 

Returns the nearest integer greater than or equal to the input number.

Ceiling numbers
gelly
1query {
2 ceil(2.5) # returns 3
3 ceil(3) # returns 3
4 ceil(-2.5) # returns -2
5 ceil(-1) # returns -1
6}

floor(number: Number!): Integer! 

Returns the nearest integer less than or equal to the input number.

Flooring numbers
gelly
1query {
2 floor(2.5) # returns 2
3 floor(3) # returns 3
4 floor(-2.5) # returns -3
5 floor(-1) # returns -1
6}

exp(number: Number!): Number! 

Returns the exponential (the constant e raised to the given power) of the input number.

Exponential of numbers
gelly
query {
exp(1) # returns 2.7182818284590452
}

ln(number: Number!): Number! 

Returns the natural logarithm (the logarithm with base e) of the input number.

Natural Logarithm of numbers
gelly
query {
ln(1) # returns 0
ln(10) # returns 2.302585092994046
}

log(number: Number!, base: Number = 10): Number! 

Returns the logarithm with base base of the input number. The base defaults to 10.

Logarithm of numbers
gelly
query {
log(10) # returns 1
log(10, base: 2) # returns 0.1505149978319906
}

mod 

There is no mod function in Gelly. Instead, use the % operator.

power(number: Number!, exponent: Number!): Number! 

Returns the given number raised to the given exponent.

Power of numbers
gelly
query {
power(10, exponent: 3) # returns 100
power(2, exponent: 5) # returns 32
}

round(number: Number!, precision: Number = 0): Number! 

Rounds the given fractional number to the nearest number of decimal places, counted by precision. Precision defaults to 0, so when not passed round rounds to an integer number.

Rounding numbers
gelly
1query {
2 round(1) # returns 1
3 round(1.11111) # returns 1
4 round(1, precision: 2) # returns 1
5 round(1.11111, precision: 2) # returns 1.11
6}

sign(number: Number!): Number! 

Returns 1 if the number is positive, -1 if the number is negative, or 0 if the number is 0.

sqrt(number: Number!): Number! 

Returns the square root of the given number.

Number square roots
gelly
query {
sqrt(4) # returns 2
sqrt(10) # returns 3.162277660168379
}

trunc(number: Number!, precision: Number = 0): Number! 

Truncates the given fractional number to the number of decimal places counted by precision. trunc doesn't do any rounding, precision is simply discarded, so if you want to round the number, see round. precision defaults to 0.

Number square roots
gelly
1query {
2 trunc(1) # returns 1
3 trunc(1.11111) # returns 1
4 trunc(1.111111, precision: 2 # returns 1.11
5 trunc(1, precision: 2) # returns 1
6}

random(): Number! 

Returns a random number greater than or equal to 0 and less than 1.

Trigonometric functions and constants 

acos(number: Number!): Number! 

Returns the inverse cosine of the input number given in radians, output in radians.

asin(number: Number!): Number! 

Returns the inverse sine of the input number given in radians, output in radians.

atan(number: Number!): Number! 

Returns the inverse tangent of the input number given in radians, output in radians.

cos(number: Number!): Number! 

Returns the cosine of the input number given in radians, output in radians.

cot(number: Number!): Number! 

Returns the cotangent of the input number given in radians, output in radians.

degrees(radians: Number!): Number! 

Converts the given input radians in radians to degrees.

pi 

Returns the value of pi, accurate to 15 decimal places.

radians(degrees: Number!): Number! 

Converts the given input degrees in degrees to radians.

sin(number: Number!): Number! 

Returns the sine of the input number given in radians, output in radians.

tan(number: Number!): Number! 

Returns the tangent of the input number given in radians, output in radians.

Type conversion functions 

cast(input: Any!, type: String!): Any! 

Converts an input expression to a new type if possible. Can fail if the value of the input can't be converted. Expects the destination type's name to be passed as a string literal, and only accepts values from the table of type names below.

Available type names:

NameNotes
String
NumberWill fail if the input can't be automatically converted to a number. Empty strings, or strings with non-numeric characters will fail.
BooleanConverts the string "0" or the empty string to false, and any other string to true.
Casting numbers to strings
gelly
query {
cast(1, type: "String") # returns "1"
cast(-42, type: "String") # returns "-42"
}
Casting strings to numbers
gelly
query {
cast("1", type: "Number") # returns 1
cast("-42", type: "Number") # returns -42
cast("apple", type: "Number") # will throw an error as the string can't be converted
}

Note: null values are not changed when casting in Gelly. casting null always returns null.

Other functions 

coalesce(input: [Any!]!>): Any! 

Coalesce with literal values
gelly
query {
coalesce([null, 1, 2]) # returns "1"
}

Returns the first non-null value in the array.