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 pagination #247

Merged
merged 14 commits into from
May 28, 2015
Merged

Support pagination #247

merged 14 commits into from
May 28, 2015

Conversation

lsegal
Copy link
Contributor

@lsegal lsegal commented May 26, 2015

See #58

lsegal added 13 commits May 26, 2015 03:27
This change adds a <Operation>Pages() operation to each operation
with pagination configuration to return an iterator that can be
used like slice iteration:

	db := dynamodb.New(nil)
	for resp := range db.ListTablesPages() {
		fmt.Println(resp)
	}

References #58
New API:

	func main() {
		db := dynamodb.New(nil)
		params := &dynamodb.ListTablesInput{Limit: aws.Long(2)}
		i := 0
		db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, err error) bool {
			if err != nil {
				fmt.Println("An error occurred", err)
			} else if p != nil {
				i++
				fmt.Println(i, awsutil.StringValue(p))
			} else {
				fmt.Println("Finished paging!")
			}
			return true // return false to stop paging
		})

	}

Also adds pagination tests.

References #58
New API:

	numPages := 0
	err := req.EachPage(func(p *dynamodb.ListTablesOutput, last bool) {
		// Print a page of data
		numPages++
		fmt.Printf("Page %d: %x\n", numPages, p.TableNames)

		if last { // We are on the last page
			fmt.Println("Got to last page")
		}
	})

	if err != nil {
		panic(err)
	}
- Document use of nil reflect.Value with Call()
- Rename "result" to "shouldContinue"
- Use "SetValueAtAnyPath" to match case insensitive matches
- Copy parameters when sending new request (add test assertion for this)
func (r *Request) EachPage(fn interface{}) error {
valfn := reflect.ValueOf(fn)
if valfn.Kind() != reflect.Func {
panic("expected function for EachPage()")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't returning error be more appropriate than panicing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not passing a function to this method would be a programmer error, not some kind of runtime error. It would never make sense to handle an error like that, since there would be no sensible recovery. Basically this should be a compile-time error but Go cannot quite represent the type here. That said, EachPage() is a low-level call that is typically not used unless you know what you're doing. The *Pages() methods have the correct type checking in them.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That argument makes sense, and I agree with the differentiation between programmer and runtime errors. But the downside of this form of runtime type checking is that you only catch the code paths you execute in your testing. 99% of the time you have a simple case and you hit the panic in development and fix it right away. But a common issue when accepting an interface{} argument is someone passing a pointer when they shouldn't have, or vice versa. And if this happens in an edge case that escaped your testing, you start panicing when releasing to production. Or worst case, code has been live and stable and some external change causes a behavior change which starts executing this bad path.

Of course the dream is 100% test coverage, and recoverer middleware can often stop panics from bubbling to the top for web servers (unless the panic is in a goroutine). But in reality edge cases can go untested, and it's nice when library code is more forgiving.

The Go authors seem to support that stance:

The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.

The above excerpt is from http://blog.golang.org/defer-panic-and-recover.

All that said, I think it's extremely unlikely anyone would hit these particular panics in production, for the reasons you mentioned. But I'm always a little uncomfortable when I see the possibility of panics escaping libraries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unfortunate reality is that simply using reflect already opens us up to arbitrary panics (Go is by definition ignoring its own above stance here), few of which we are guarding from because they are all equally edge-cased programmer errors, but there are cases where, for instance, fn.Call() could fail for various reasons, etc. -- all of those are panics.

Just to reinforce a few things though: the likelihood of an actual panic in production code is really low. You would have to do bad things to create such a state such that your code could not have ever worked. These are mostly just edge case guards to generate "nicer" panics than the ones reflect will emit. The goal isn't to try to defensively guard against these scenarios.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An option would be to use a recoverer, like the encoding/json package.

But do I agree that this would be extremely rare in production.

Also refactor and add tests for truncation logic
lsegal added a commit that referenced this pull request May 28, 2015
Adds support for pagination
@lsegal lsegal merged commit f5a07a3 into master May 28, 2015
}
if valfn.Type().NumOut() > 0 && valfn.Type().Out(0).Kind() != reflect.Bool {
panic("EachPage(fn) function must return bool if it returns a value")
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also want to check the input types here:

  • takes two values
  • the first one must be ???
  • the second one must be a boolean

@lsegal lsegal deleted the paginators branch June 2, 2015 21:57
lsegal added a commit that referenced this pull request Jun 2, 2015
Based on a suggestion from @nightlyone we were able to remove the
use of reflection. This change subtly changes the API on the low-level
Request.EachPage() operation. This does not change the wrapped pager
functions for each operation on the service, specifically the
"*Pages()" pagination operations. The new EachPage API is:

	EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error

Note that "data" is no longer the response type structure but a simple
interface{} type. In order to use the structure, the value must be
manually asserted to the response type:

	// Get the tables in a page of data
	tables := *data.(*dynamodb.ListTablesOutput).Tables

It is also no longer possible to omit the "shouldContinue" return
value to the function.

References #247
wking added a commit to wking/openshift-installer that referenced this pull request Aug 14, 2019
Sometimes CI leaks untagged subnets.  Because we are allowed to remove
all resources from within a cluster-owned VPC, add a ByVPC walker to
remove these indirectly-owned subnets.

DescribeSubnetsPages has a strange history.  It was initially added in
aws/aws-sdk-go@3664ecc7da (Add initial implementation of pagination,
2015-03-23, aws/aws-sdk-go#247) but was removed in
aws/aws-sdk-go@bad551feb8 (Add support for multi-token pagination
rules, 2015-03-27) as a later step in that same pull request.  It
finally landed in master via aws/aws-sdk-go@52cd98f1ed (Release
v1.19.30, 2019-05-14, aws/aws-sdk-go#2599), but we only vendor
v1.16.14.  It doesn't seem like "zounds of untagged subnets" is a
high-probability thing, so I'm just using the unpaginated
DescribeSubnets instead of bumping the vendor to pick up
DescribeSubnetsPages.  Even if we do overflow DescribeSubnets with
untagged subnets, VPC deletion will fail and we'll get another pass at
deleting tagged subnets when we come around to the next deleteEC2VPC
attempt.
jhixson74 pushed a commit to jhixson74/installer that referenced this pull request Dec 6, 2019
Sometimes CI leaks untagged subnets.  Because we are allowed to remove
all resources from within a cluster-owned VPC, add a ByVPC walker to
remove these indirectly-owned subnets.

DescribeSubnetsPages has a strange history.  It was initially added in
aws/aws-sdk-go@3664ecc7da (Add initial implementation of pagination,
2015-03-23, aws/aws-sdk-go#247) but was removed in
aws/aws-sdk-go@bad551feb8 (Add support for multi-token pagination
rules, 2015-03-27) as a later step in that same pull request.  It
finally landed in master via aws/aws-sdk-go@52cd98f1ed (Release
v1.19.30, 2019-05-14, aws/aws-sdk-go#2599), but we only vendor
v1.16.14.  It doesn't seem like "zounds of untagged subnets" is a
high-probability thing, so I'm just using the unpaginated
DescribeSubnets instead of bumping the vendor to pick up
DescribeSubnetsPages.  Even if we do overflow DescribeSubnets with
untagged subnets, VPC deletion will fail and we'll get another pass at
deleting tagged subnets when we come around to the next deleteEC2VPC
attempt.
skotambkar pushed a commit to skotambkar/aws-sdk-go that referenced this pull request May 20, 2021
Updates the v2 SDK to use the latest go-jmespath.

Fix aws#247
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 this pull request may close these issues.

None yet

3 participants