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

Support Lazy Enumerators with Stream #4907

Open
rmosolgo opened this issue Apr 10, 2024 · 3 comments · May be fixed by #4920
Open

Support Lazy Enumerators with Stream #4907

rmosolgo opened this issue Apr 10, 2024 · 3 comments · May be fixed by #4920

Comments

@rmosolgo
Copy link
Owner

rmosolgo commented Apr 10, 2024

Currently @stream will defer child selections on list items, but all items are enumerated before any child selections are run.

A better workflow might be that, when a @streamed list is resolved, there's no call to .each right away. Instead, and empty list is put in the response, and then each deferral gets the item it needs and resolves its subfields.

Here's a script to demonstrate the behavior in question:

`@stream` with lazy enumeration

require "bundler/inline"

gemfile do
  gem "graphql", path: "./"  # "~>2.2.0"
  gem "graphql-pro", "~>1.0"
end

class MySchema < GraphQL::Schema
  class LazyItems
    DATA = [
      OpenStruct.new(name: "Stopwatch"),
      OpenStruct.new(name: "Necktie"),
      OpenStruct.new(name: "Suitcase"),
      OpenStruct.new(name: "Chopstick"),
      OpenStruct.new(name: "Peanut"),
      OpenStruct.new(name: "Screwdriver"),
    ]


    def each
      idx = 0
      while idx < DATA.length
        item = DATA[idx]
        puts "Yielding #{idx}: #{item.name}"
        yield(item)
        idx += 1
      end
      self
    end
  end

  class Item < GraphQL::Schema::Object
    field :name, String

    def name
      puts "Resolving Item#name: #{object.name}"
      object.name
    end
  end

  class Query < GraphQL::Schema::Object
    field :items, [Item]

    def items
      puts "Resolving items"
      LazyItems.new
    end
  end

  query(Query)
  use GraphQL::Pro::Stream
end

query_str = "{ items @stream { name } }"

res = MySchema.execute(query_str)
res.context[:defer].each do |deferral|
  deferral.to_h
end
pp res.to_h

It should alternate between yield and resolve messages, but instead, it does all the yields then all the resolves.

If the implementation supported lazy enumeration, then you could use @stream to break up database requests into smaller chunks using a cursor.

@rmosolgo rmosolgo linked a pull request Apr 17, 2024 that will close this issue
3 tasks
@tgwizard
Copy link

We're currently looking into using streaming more for our APIs for the https://shop.app. Because the current lack of support for lazy enumerators, per this issue, we have all such APIs separate from our main APIs, and don't use GraphQL. Big ❤️ for this.

@rmosolgo
Copy link
Owner Author

Hey, thanks for chiming in, @tgwizard. I started looking into the implementation for this and determined it was going to be ... tricky. It's still definitely my goal ... but no low-hanging fruit here 😅

@tgwizard
Copy link

@rmosolgo yeah, I got that from the trampolining comment in the PR 😄 However I can help I'd be happy to do it.

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

Successfully merging a pull request may close this issue.

2 participants