Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interpreter overrides the child bindings in recursive context #724

Open
buremba opened this issue Aug 3, 2021 · 10 comments
Open

Interpreter overrides the child bindings in recursive context #724

buremba opened this issue Aug 3, 2021 · 10 comments

Comments

@buremba
Copy link

buremba commented Aug 3, 2021

We have a Jinja expression as follows:

{{someotherexpression}}

The someotherexpression is actually another Jinja expression and we render the actual value in the toString() of someotherexpression. Since they run in the same thread, JinjavaInterpreter.getCurrent() returns the parent interpreter for someothercontext but we're not able to use our bindings in this context.

In the case above, Jinjava gets the context for the parent interpreter and overrides all the values in the current context as follows:
https://github.com/HubSpot/jinjava/blob/master/src/main/java/com/hubspot/jinjava/Jinjava.java#L234

I believe that we should use the following snippet in order not to override the child context:

// removed this:  bindingsWithParentContext.putAll(parentInterpreter.getContext());
for (Map.Entry<String, Object> entry : bindingsWithParentContext.entrySet()) {
    bindingsWithParentContext.putIfAbsent(entry.getKey(), entry.getValue());
}
@jasmith-hs
Copy link
Contributor

@hs-lsong this seems right to me, but I don't know the full context around the time when this code was initially added in #447

@buremba
Copy link
Author

buremba commented Aug 6, 2021

@jasmith-hs Just you give you some context, I had to make the change that I suggested in order to make it work for my use-case. I had a snippet something like
jinjava.render('{{myvalue}}', mapOf('myvalue' to 5)) and somehow the output was 10 instead of 5. It was because the parent context has a variable myvalue set to 10 so no matter which value I pass, it was overridden.

@hs-lsong
Copy link
Collaborator

hs-lsong commented Aug 6, 2021

Thanks for filing this issue. Do you have a complete test so that we can run and confirm?

@buremba
Copy link
Author

buremba commented Aug 7, 2021

Sure, here it is:

    @Test
    fun recursiveExpression() {
        val renderer = Jinjava()
        println(renderer.render("{{proxy_call}}", mapOf("proxy_call" to ProxyCall(renderer), "globalvar" to 10)))
    }

    class ProxyCall(val renderer: Jinjava) {
        override fun toString(): String {
            return renderer.render("{{globalvar}}", mapOf("globalvar" to 5))
        }
    }

@hs-lsong
Copy link
Collaborator

hs-lsong commented Aug 9, 2021

Thanks for the example. I think it is dangerous to have recursive call for the rendering. Probably we should disable this.

@buremba
Copy link
Author

buremba commented Aug 9, 2021

@hs-lsong any alternative approach for what I'm trying to do? I don't want to use functions for convenience (better UX for us) and I can only render the values in a lazy way since it's expensive to fetch the values.

@hs-lsong
Copy link
Collaborator

hs-lsong commented Aug 9, 2021

It seems you can just call this directly, no?
renderer.render("{{globalvar}}", mapOf("globalvar" to 5))

@buremba
Copy link
Author

buremba commented Aug 9, 2021

@hs-lsong, I probably oversimplified the example. :) Here is our renderer: https://github.com/metriql/metriql/blob/master/src/main/java/com/metriql/service/jinja/JinjaRendererService.kt

We let users define their data models (metrics) as Jinja expressions embedded into SQL and we render SQL for the underlying database using Jinja under the hood.

@rdehuyss
Copy link
Contributor

I'm also having troubles with this.

@hs-lsong - why do you think it is dangerous to have recursive call for rendering? It allows for some really clean code and nice constructs.

@boulter
Copy link
Contributor

boulter commented Apr 24, 2023

If you trust the code, then recursion is fine. In HubSpot's case, we have code submitted by users who often don't terminate their code correctly or create cycles which are run in shared environments which is why we avoid recursion.

boulter added a commit that referenced this issue Apr 24, 2023
Small refactoring related to #724
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants