FQLv10 string iterpolation of system collection names

Hello,

So I’m running into something while converting FQLv4 code to FQLv10. In some cases, I use a python variable to refer to a collection or index name which I reference in a FQL query to do something with that collection. Where in FQLv4 I can do something like:

migrations_index_name = "by_name_and_namespace"
# somewhere later in the same file or a different file...
query.match(
    query.index(migrations_index_name),
    [query.var("migration_name"), query.var("migration_namespace")],
)

However, now that indexes are part of the collection, it seems I cannot do a similar thing using FQLv10 driver string interpolation. This code snippet below results in an error that a string has no method:

migrations_collection_name= "by_name_and_namespace"
# somewhere later in the same file or a different file...
fauna_v10_client.query(
    fql(
        """
        let migrations = ${migrations_tuple}
        migrations.every(
            migration => {
                ${migrations_collection_name}.by_name_and_namespace(
                    migration.name, migration.namespace
                ) != null
            }
        )
        """,
        migrations_tuple=tuple(
            {"name": migration.name, "namespace": migration.namespace}
            for migration in migrations
            if migration.child_database is None
        ),
        migrations_collection_name=migrations_collection_name,
    )
)

This makes sense, because the string interpolation expects to replace the placeholder with some kind of literal value. However, since the FQLv10 drivers use strings rather than python objects to construct queries, it seems that the ability to build more generic queries where the system collection name (index, collection, etc…) is dynamic has been lost. The byName flavor of utility methods on things such as Collection.byName don’t seem to replace this either, as that returns the definition of the collection not a handle to the collection itself. So something like Collection.byName(${migrations_collection_name}).by_name_and_namespace() does not work.

The best workaround I’ve come up with so far is to mix fql string interpolation with python native f-strings, which in my opinion is confusing:

migrations_collection_name= "by_name_and_namespace"
# somewhere later in the same file or a different file...
fauna_v10_client.query(
    fql(
        f"""
        let migrations = ${{migrations_tuple}}
        migrations.every(
            migration => {{
                {migrations_collection_name}.by_name_and_namespace(
                    migration.name, migration.namespace
                ) != null
            }}
        )
        """,
        migrations_tuple=tuple(
            {"name": migration.name, "namespace": migration.namespace}
            for migration in migrations
            if migration.child_database is None
        ),
    )
)

Is there another alternative I’m missing? If not, is there a plan to allow for some way in FQLv10 to be able to refer to a system collection name dynamically in a FQLv10 query body?

You are correct that when you provide a string as an argument, it gets interpreted as a string. The fql function eliminates injection attacks by treating arguments this way.

You can ensure that your argument is interpreted as a module by creating an instance of Module class.

from fauna.query.models import Module

migrations_collection = Module("MyCollectionName")

Please be careful working around the built-in query interpolation, as it can introduce injection attack opportunities. In your workaround, the name can be replaced with a block that performs arbitrary work.

migrations_collection_name="""{
  // sneaky attack that deletes stuff
  Database.all().forEach(c => c.definition.delete())

  // return the value from the block like nothing happened
  MyCollectionName
}"""
1 Like

If you know the names in advance, and do not need to work with dynamic Collection names, then you can use fql to defined the collection instead of providing a Module instance as an argument later.

migrations_collection = fql("MyCollectionName")

# later
fql(
    "{migrations_collection}.by_name_and_namespace(...)",
    migrations_collection=migration_collection)
1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.