Improve FQL

Use something more intuitive than the current FQL syntax to stand out among other cloud services.
My suggestions:

Create

createDatabase("app");

createDatabase(["app", "barbearia"]);

__________________________________________________________

database("app").createCollection("Users");

database("app").createCollection(["Users", "Products"]);

Insert

database("app").collection("Users").insertDocument(
{
  _id: ID(5ds564654fd),
  nome: "Adalberto",
  idade: 48
}, { _id, nome }); // the second parameter is optional and works like in GraphQL

database("app").collection("Users").insertDocument([
{
  _id: ID(df4547454h),
  nome: "Ronaldo",
  idade: 17
},
{
  _id: ID(gf78hy498g),
  nome: "Vicente",
  idade: 32
}
], { _id, nome }); // the second parameter is optional and works like in GraphQL

Find

database("app").collection("Users").findDocument(user -> user.nome == "Ronaldo" && user.idade > 8, { _id, nome }); // the second parameter is optional and works like in GraphQL

database("app").collection("Users").findDocument(({ nome, idade }) -> nome == "Ronaldo" && idade > 8, { _id, nome }); // the second parameter is optional and works like in GraphQL

Update

database("app").collection("Users").updateDocument(
  user -> user.nome == "Ronaldo" && user.idade > 8,
  user: {
    idade: 47
  },
{ _id, nome }); // the second parameter is optional and works like in GraphQL

database("app").collection("Users").updateDocument(
  ({ nome, idade }) -> nome == "Ronaldo" && idade > 8,
  user: {
    idade: 47
  },
{ _id, nome }); // the second parameter is optional and works like in GraphQL

Delete

database("app").collection("Users").deleteDocument(user -> user.nome == "Ronaldo" && user.idade > 8, { _id, nome }); // the second parameter is optional and works like in GraphQL

database("app").collection("Users").deleteDocument(({ nome, idade }) -> nome == "Ronaldo" && idade > 8, { _id, nome }); // the second parameter is optional and works like in GraphQL

Hey, thanks for the suggestion. I took the liberty to remove the opinionated part though, every programmer likes different styles and each style has advantages/disadvantages. You’ll easily find a wealth of pros for functional composition style as well. FQL might appeal to Lisp programmers while the dot syntax might appeal to others.

We are currently looking into FQL and one of the options that is considered is close to what you are suggesting since it has indeed quite some advantages indeed. However, there are disadvantages as well, try to write something more complex than what you’ve written here with conditional statements, multiple variables and/or try to compose this kind of FQL programmatically (things that the current FQL excels at). However, FQL is indeed overly verbose, and maybe a hybrid of dot syntax with something else might be the answer. Many options are being considered at this point and evaluated against both simple and complex examples so that both become easier to write.

1 Like

If it matters, I found FQL annoying initially but once I got comfortable it gets out of my way, I write complex queries in a couple of minutes.

“However, FQL is indeed overly verbose, and maybe a hybrid of dot syntax with something else might be the answer”

I totally agree, in my perspective, class is completely out of date and has no use compared to traits

my favorite programming language is Julia and it’s fundamentally functional and structural, it doesn’t have traits yet. But the own Julia has a optional operator |> for the syntax to be closer to the dot syntax

You can create your own Fauna domain language!

If desired, you can write this functionality on your own. It is precisely because FQL’s functional style that allows this sort of thing.

const initDB = (secret) => {
  const client = new Client({secret})

  return {
    createCollection: (name) => client.query(q.CreateCollection({ name )),
    collection: (name) => {
      const collectionRef = q.Collection(name)
      return {
        documents: () => client.query(q.Paginate(q.Documents(collectionRef))),
        insertDocument: (data) => client.query(q.Create(collectionRef, { data })),
        /* etc */
      }
    }
  }
}

FQL will stick around to do complex things

If you rely too heavily on your own wrapper language you risk it become restrictive. Either your domain language will explode or you’ll fall back to plain FQL when you need to. A mix of both is very reasonable, though!

My experience suggests sticking to setup type queries. That is, build helpers to create Collections, Indexes, Functions, and Role. Do not try to incorporate client facing CRUD ops.

Brecht is definitely correct. A very large proportion of queries I’ve worked on are more than simple create and find type. Database setup is full of conditionals and error handling (e.g. If(Exists(Collection(...)))). A lot of client-side app functionality is actually built into complex UDFs, some of which perform multiple operations on multiple Collections.

Constructing a separate way of writing queries can be really valuable. My advice, though is that you focus on what can be done to make common but complex operations simpler to write, or to make organizing 100s of lines of setup code easier to read. Do not try to replace the core functionality of FQL – see what you can do to expand it! And share! I like to read that stuff :nerd_face:

An example I had fun with

I created some helpers for myself that mimicked an Express server and I enjoyed working with that (for DB setup. Again, not for client side queries). It helped the following

  • Separate concerns into small modules and then app.use() them.
  • The different portions of an app are split into multiple files, making maintaining and growing the app easier.
  • Builds up a list of setup queries that are postponed until you hit go (app.bootstrap()).
  • Each function adds on some amount of error handling and debugging information. When it executes, there’s a lot of good and well formatted information sent to the console.
  • Chunks of GraphQL schema are all stitched together and sent in one import call.

I’m not actually showing any of that, but I want to highlight that I was not trying to replace FQL. Instead I wrapped it up into things that helped accomplish other goals: modularity, error handling, logging, GraphQL integration, etc.

// modules/BasicUser.js
const BasicUser = (app) => {
  const module = app.module('BasicUser')

  module.schema(
    `type User {
      name: String!
      email: String! @unique
    }

    type Query {
      allUsers: [User!]!
    }
  `)

  module.role({
    name: 'User',
    membership: [{ source: q.Collection('User') }],
    privileges: [/* ... */]
  })
}

// modules/Todos.js
const Todos = (app) => {
  const module = app.module('Todos')

  module.schema(
    `type Todo {
      description: String!
      completed: Boolean!
      owner: User! @relation
    }

    extend User {
      todos: [Todo]
    }

    type Query {
      allTodos: [User!]!
    }
  `)

  module.role({
    name: 'User_Todos',
    membership: [{ source: q.Collection('User') }],
    privileges: [/* ... */]
  })

  module.role({
    name: 'fn_UserCreateTodo',
    privileges: [/* ... */]
  })

  module.function({ 
    name: 'UserCreateTodo',
    role: q.Role('fn_UserCreateTodo')
    /* ... */ 
  })
}

// modules/UnisedTokens.js
const UnusedTokens = (app) => {
  const module = app.module('UnusedTokens')
  
  module.index({
    name: 'tokens_ttl',
    source: q.Tokens(),
    values: [{ field: ['ttl'] }, { field: ['ref'] }]
  })

  module.function({
    name: 'deleteExpiredTokens',
    /* ... */
}

// setup.js
const app = initApp()
app.use(BasicUser)
app.use(Todos)
app.use(UnusedTokens)
app.bootstrap()
1 Like