orbitalapi / demos

A series of demos, showing features of Orbital and TaxiQL
https://orbitalhq.com
Apache License 2.0
1 stars 1 forks source link

Brain dump of demos #7

Open martypitt opened 1 week ago

martypitt commented 1 week ago

Add comments to this issue to capture demos that we need to build. We'll review, then split them out into seperate issues

martypitt commented 6 days ago

This is a dump of interesting snippets we need to document / showcase in Taxi playground.

Add to this rough 'n' ready whenever we encounter something that we want to ensure is shown.

One's we've built on-the-fly:

can use a variable from a saved query in a field projection

      query FindSomeFilms( starring : PersonName ) {
         find { Film[] } as  {
            title: Title
            starring: filter(Actor[], (PersonName) -> PersonName == starring) as {
              name : PersonName
           }[]
         }[]
      }

Filtering a stream using .filterEach()

Defining / refining scope in a projection:

assigning a name to the scope:

See can project using a named scope at the top level

find { Film[] } as (film:Film) -> {
   movieName : film.title
}[]

Refining to a property:

see: a projection can refine whats in scope

            model Film {
               title : Title inherits String
               cast : Actor[]
            }
            model Actor {
               name : Name inherits String
            }

find { Film } as (Actor[]) -> {
  actorName : Name
  filmTitle : Title // should be null, as it's out-of-scope on Actor
}[]

Refining using a function

see: expressions in projection scopes can trigger discovery:

         find { Film } as (first(Actor[])) -> { // note that film has been removed from the scope...
            title : Title //... so we expect this isn't discoverable.
            starring : ActorName
         }

Alternatively, with film in scope:

         find { Film } as (Film, first(Actor[])) -> { // Here, Film is in scope...
            title : Title // .. so this is knowable
            starring : ActorName
         }

not sure what to call this:

         find { Film[] } as (film:Film) -> {
               title : FilmTitle
               star : singleBy(film.cast, (Actor) -> Actor::ActorId, film.headliner) as (actor:Actor) -> {
                  name : actor.name
                  title : film.title
               }
            }[]

Note - if we make singleBy an extension function (and we should), that gets simplified to:

         find { Film[] } as (film:Film) -> {
               title : FilmTitle
               star : film.cast.singleBy((Actor) -> Actor::ActorId, film.headliner) as (actor:Actor) -> {
                  name : actor.name
                  title : film.title
               }
            }[]

Projecting an inline array:

see ProjectionsOnArraysTest.wtf:

            type FilmId inherits Int
            model Film {
               title : FilmTitle inherits String
               cast : CastId inherits Int
            }
            model Actor {
               name : PersonName inherits String
            }
            model FilmCast {
               actors : Actor[]
            }
            service Movies {
               operation findFilm(FilmId):Film
               operation getCast(CastId):FilmCast
            }

Queried, loads from multiple services:

         given { FilmId = 1 }
         find { Film } as {
            actor : first(Actor[]) as {
               firstName : PersonName
            }
         }

Scoped params on tests:

Passing a filmId as input to query

query FindFilm( filmId : FilmId ) {
   find { Film( FilmId == filmId ) }
}

Casting

see: can use a cast expression against a parameter in a given clause

         type PersonId inherits String
         type HumanId inherits String

         query findPerson(personId : PersonId) {
            given { humanId : HumanId = (HumanId) personId }
            find { human : HumanId }
         }

Casting in a given clause:

see: can use a cast expression in a given clause

         type PersonId inherits String
         type HumanId inherits String

            given { humanId : HumanId = (HumanId) "123" }
            find { human : HumanId }

Using a query param in a function

In this example, we're filtering at the top level:

     query FindSomeFilms( starring : PersonName ) {
      find { Film[] } as (filter(Actor[], (PersonName) -> PersonName == starring)) -> {
         name: PersonName
      }[]

(see: can use a variable from a saved query in projection type expression)

can also filter at the field level:

      query FindSomeFilms( starring : PersonName ) {
         find { Film[] } as  {
            title: Title
            starring: filter(Actor[], (PersonName) -> PersonName == starring) as {
              name : PersonName
           }[]
         }[]
      }

(see: can use a variable from a saved query in a field projection)

Collections

Can use properties of collections to discover other collections:

see: can populate a collection attrib with a value returned from a service

         model Person {
            @Id
            id : PersonId inherits Int
            name : PersonName inherits String
         }
         model Friend inherits Person
         service Foo {
            operation findAllPeople():PersonId[]
            operation findPerson(PersonId):Person
            operation findAllFriends(PersonId):Friend[]
         }

query:

find { PersonId[] } as {
 name : PersonName
  friends : Friend[]
}[]

retrurns:

[
  {
    "name": "Doug",
    "friends": [
      { "id": 1, "name": "Jimmy" },
      { "id": 2, "name": "Jack" }
    ]
  }
]

Using ids in collections

see: given an array of discovered values, ids present in those arrays can look up attributes from other types

Given a movie:

         model Movie {
            title : MovieTitle inherits String
            cast : Cast
         }
         model Cast {
            actors : ActorId[]
         }

And an actor:

         model Actor {
            @Id
            id : ActorId
            name : ActorName inherits String
         }

Data is returned from seperate services:

         service MovieService {
            operation findAllMovies():Movie[]
            operation findActor(ActorId):Actor
         }

We can traverse the collections:

find { Movie[] } as {
   movieTitle: MovieTitle
   actors: ActorName[]
}

Here's that example as a Voyager example:

import {StubQueryMessageWithSlug} from "../../app/services/query.service";

export const example: StubQueryMessageWithSlug = {
   "title": "Title goes here",
   "slug": "slug-goes-here",
   "query": {
   "schema": ` // Given a movie:

 type ActorId inherits Int
 model Movie {
    title : MovieTitle inherits String
    cast : {
        actors: ActorId[]
    }
 }

// And an actor:
 model Actor {
    @Id
    id : ActorId
    name : ActorName inherits String
 }

// Data is returned from different APIs:
 service MovieService {
    operation findAllMovies():Movie[]
    operation findActor(ActorId):Actor
 }
`,
   "query": `
// We can traverse these collections:
find { Movie[] } as {
   movieTitle: MovieTitle
   actors: ActorName[]
}[]

`,
   "parameters": {},
   "stubs": [
      {
         "operationName": "findAllMovies",
         "response": "  [ { \"title\" : \"The ducks take Manhattan\", \"cast\" : { \"actors\" : [1,2,3] } } ]"
      },
      {
         "operationName": "findActor",
         "response": "{ \"name\" : \"Mickey Mouse\" }"
      }
   ]
}
}

Joining streams

Joining streams using |

      service TweetService {
         operation tweets():Stream<Tweet>
         operation analytics():Stream<TweetAnalytics>
         operation getUser(UserId):User
      }

stream { Tweet | TweetAnalytics }
  as {
     id : MessageId
     body : Message
     views : ViewCount?
}[]

Mutations

Output of a stream / query is transformed to the input of a mutation function

see: `can call mutation for each member of a stream``

// given a stream:
         model UserUpdateMessage {
            userId : UserId inherits String
            message : StatusMessage inherits String
         }

service UserUpdates {
   stream updates: Stream<UserUpdateMessage>
}

// Elsewhere, User is defined:
         model User {
            id : UserId
            name : UserName inherits String
         }

         service UserService {
            operation getUser(UserId):User
         }

// Finally, we want to persist updates, combining the message and user information:

         parameter model RichUserUpdateMessage {
            userId : UserId
            name : UserName
            message : StatusMessage
         }

         service UserService {
             write operation storeUpdate(RichUserUpdateMessage):RichUserUpdateMessage
         }

// The query:
         stream { UserUpdateMessage }
         call UserService::storeUpdate

// Will transform UserUpdateMessage into  RichUserUpdateMessage, enriching it by calling getUser()

Parameter and Closed

      closed parameter model Film {
         @Id
         filmId : FilmId
         title : Title
      }
      @HazelcastService(connectionName = "notUsed")
      service HazelcastService {
         @UpsertOperation
         write operation upsert(Film):Film

         @DeleteOperation(mapName = "films")
         write operation deleteAll()

         @DeleteOperation(mapName = "films")
         write operation deleteByKey(FilmId):Film

         table films : Film[]
      }

Converting things

Converting using as

converting using convert()

see can convert from one type to another using convert

      [[
      Converts the provided source into the target type reference.
       Conversions are performed locally, using only the data provided in source - ie.,
       no services or graph searches are performed.

       This method is less powerful than using a standard projection (eg., A as B), because:

        - Only the exact facts passed in the source are considered
        - No graph searches or remote invocations are performed

        As a result, it's also more performant.
       ]]
      declare function <T> convert(source: Any, targetType: lang.taxi.Type<T>): T"""
         model Person {
            name : FirstName inherits String
            age : PersonAge inherits String
         }
         model Dude {
            knownBy : FirstName
         }
         model Thing {
            person : Person
            dude : convert(this.person, Dude)
         }

Spread operator

see: generates the correct fields with excluded fields

... and ... except:

find { Person } as {
               name : FirstName
               address : Address as {
                 isOddNumbered : Boolean
                 house : HouseNumber
                 ... except {secretCode}
               }
               ... except {secretAge}
            }

Projection shorthand

find { Person } as {
   name, // reads name from Person
   address // etc
}

Restricting which services get called:

These examples from TaxiQlServiceRestrictionsSpec:

Only calling services with using:

         closed model Film {
            title : Title inherits String
         }
         service FilmService {
            operation getFilms():Film[]
            operation getBlockbusters():Film[]
         }

         find { Film[] }
         using { FilmService::getFilms }

Mixing services and operations:

            service NetflixService {
               operation getFilms():Film[]
               operation getBlockbusters():Film[]
            }

            find { Film[] }
            using { FilmService::getBlockbusters , FilmService::getFilms, NetflixService }

Excluding a service:

            find { Film[] }
            excluding { FilmService::films }