Querying a one-to-many relation starting from an array of foreign keys

Hi all, i’m really new to Fauna, i’ve hit a but of a stumbling block with how to push an array of objects… Can anyone help:

This is my query

client.query(
      q.Create(q.Collection(COLLECTION_NAME), {
        data: {
          slug: "some-slug",
          things: [{ name: "testA" }, { name: "testB" }],
        },
      })

I have an index setup with slug as the term and the values to return are slug and things but things always comes back null… what am i missing?

Thanks in advance

@databrecht

Can you share the FQL for the Index?

1 Like
{
  name: "get-things-by-slug",
  unique: false,
  serialized: true,
  source: "demo-blog",
  terms: [
    {
      field: ["data", "slug"]
    }
  ],
  values: [
    {
      field: ["ref"]
    },
    {
      field: ["data", "slug"]
    },
    {
      field: ["data", "things"]
    }
  ]
}
1 Like

@PaulieScanlon can you share how you’re querying the data?

client.query(
      q.Paginate(q.Match(q.Index('get-things-by-slug'), "some-slug")),
    );

@PaulieScanlon I think what’s going on is that Fauna is trying to sort the results using the array value, and since obviously that is not possible, it just returns null for the array.

Maybe @databrecht can enlighten us about what’s going on here. :slight_smile:

Here are a couple of quick solutions. Do you need to sort the results by slug?

If no, then you could simply remove the values from your index, and then query the index as usual to get an array of documents:

Map(
  Paginate(Match(Index("get-things-by-slug"), "some-slug")),
  Lambda("thingRef", Get(Var("thingRef")))
)

If you do need to sort by slug, you could remove the ["data", "things"] value from your index and do something like this which should get you an array of documents too:

Map(
  Paginate(Match(Index("get-things-by-slug"), "some-slug"),
  Lambda(["ref", "slug"], Get(Var("ref")))
)

Ah ha, yeah this is interesting. I had stumbled on Lambda but have to be hones didn’t really understand it. They’ll only ever be one “things” array per slug… lemme give that a try and see what’s what… Thanks so much

Ah ok, then you can simply remove the values from your index and do this:

Get(Match(Index("get-things-by-slug"), "some-slug"))

Which should give you the first document that matches without using Lambda.

Edit:

I’m writing a series of introductory articles for Fauna which I think you will find helpful.

This is the first one:

This did the trick

client
      .query(
        q.Map(
          q.Paginate(q.Match(q.Index('get-things-by-slug'),"some-slug")),
          q.Lambda('X', q.Get(q.Var('X')))
        )
      )
      .then((response) => {
        console.log(JSON.stringify(response.data, null, 2));
      })
1 Like

Hi Paulie,

I took the liberty to change the title so other people can understand the topic more easily and find information they might need. The question is slightly different from your original question on twitter. I’ll list that here for people to know where the original question comes from:


// This is my data in a Fauna collection

{
  "ref": Ref(Collection("demo-blog"), "269039036074557959"),
  "ts": 1592834468830000,
  "data": {
    "slug": "some-slug",
    "things": [
      {
        "name": "testA",
      },
      {
        "name": "testB"
      }
    ]
  }
}


// This is how i create the above
    client.query(
      q.Create(q.Collection(COLLECTION_NAME), {
        data: {
          slug: "some-slug",
          things: [{ name: "testA" }, { name: "testB" }],
        },
      })
    );

// This is how i read the above
  client.query(
    q.Paginate(q.Match(q.Index("get-things-by-slug"), "some-slug"))
  );

// This is my GraphQL query
const GET_THINGS_BY_SLUG = gql`
  query($slug: String!) {
    getThingsBySlug(slug: $slug) {
      ref
      slug
      things
    }
  }
`;

// and these are my typeDefs

const typeDefs = gql`
  type Query {
    getThingsBySlug(slug: String!): [ThingsObject]
  }
  type Thing {
    name: String
  }
  type ThingsObject {
    ref: String
    slug: String
    things: [Thing]
  }
`;

// This is what i see returned
{
  data: [
    [
      Ref(Collection("demo-blog"), "269039036074557959"),
      'some-slug',
      null
    ]
  ]
} 


This was the code you shared with us on twitter. In order to test it out I had to modify the schema slightly since you didn’t specify embedded nor relations so I’m not sure what your GraphQL schema generated. I assume that this was what you tried to do? (have a relation thingObject?)

type Query {
   getThingsBySlug(slug: String!): [ThingsObject]
 }
 type Thing {
   name: String
   thingObject: ThingsObject @relation
 }
 type ThingsObject {
   ref: String
   slug: String
   things: [Thing] @relation
 }

If I would now write:

mutation C {
  createThingsObject(data:{
    things: {
       create: [{ name: "test" }]
    }
  })
  {
   _id
  }
}

In GraphQL, this will create me a Thing and a ThingsObject


In your original question you did not create those with GraphQL but instead did that with FQL:

    client.query(
      q.Create(q.Collection('ThingsObject'), {
        data: {
          slug: "some-slug",
          things: [{ name: "testA" }, { name: "testB" }],
        },
      })
    );

That’s a totally different thing. FQL will ignore the schema and what you specified here is that you are just going to store an array in the ThingsObject:


It will not create any ‘Things’ and if I’m not mistaking, that’s your first point of confusion. If you store it like this, whatever GraphQL query you will do, it will not find the references to things it is looking for and it will always return null.

If you would like to store the same structure as in the GraphQL query via FQL, you’ll have to first create the things, (e.g in a loop via Map) use Let to temporary store the references that this creation returns and then create the ThingsObject where you just store an array of references to the things, and not just inline the data.

For the actual querying, it seems you indeed already have found how to get things by slug.

   q.Map(
          q.Paginate(q.Match(q.Index('get-things-by-slug'),"some-slug")),
          q.Lambda('X', q.Get(q.Var('X')))
        )

Should work fine if your index only contains references in the values.
In your original index:

{
  name: "get-things-by-slug",
  unique: false,
  serialized: true,
  source: "demo-blog",
  terms: [
    {
      field: ["data", "slug"]
    }
  ],
  values: [
    {
      field: ["ref"]
    },
    {
      field: ["data", "slug"]
    },
    {
      field: ["data", "things"]
    }
  ]
}

You have multiple values so I assume you have changed your index in the meantime and changed what values it returns. If you have three values, you will have to change the parameters that the lambda takes:

   q.Map(
          q.Paginate(q.Match(q.Index('get-things-by-slug'),"some-slug")),
          q.Lambda(['ref','slug', 'things'], q.Get(q.Var('ref')))
        )

However, since you only need the ref clearly, there is no need to add the other values.

Thanks @databrecht… i’ve become a bit lost in all of this… i’ll update later with where i’m at as i’ve changed a few things along the way.