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

feature: Allow custom window functions to be registered with the driver #1220

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

ohaibbq
Copy link

@ohaibbq ohaibbq commented Feb 11, 2024

This implements the registration of user-defined window functions. It extends the existing RegisterAggregator interface to allow the impl to implement Inverse and Value, in which case, the aggregator can be used both as a regular aggregate function and a window function.

Closes #1215

@ohaibbq
Copy link
Author

ohaibbq commented Mar 11, 2024

Hi @mattn, how can I help in getting this merged?

@mattn
Copy link
Owner

mattn commented Mar 15, 2024

Sorry my delay. I'll look into it in later.

@mattn
Copy link
Owner

mattn commented Mar 15, 2024

Look good for me. @rittneje any opinion?

@ohaibbq
Copy link
Author

ohaibbq commented Mar 28, 2024

A friendly bump on this @rittneje

@ohaibbq
Copy link
Author

ohaibbq commented Apr 11, 2024

@mattn This would be very helpful to get in for the users of https://github.com/goccy/bigquery-emulator I cannot merge my feature branch into the project as it uses a forked go-sqlite3.

Is there anything else I could do to push this along?

@mattn
Copy link
Owner

mattn commented Apr 11, 2024

Yes, I'm thinking this is useful but I want to wait @rittneje 's opinion.

@ohaibbq
Copy link
Author

ohaibbq commented Jun 12, 2024

Following up again!

@ohaibbq
Copy link
Author

ohaibbq commented Jun 27, 2024

@rittneje Did you have any thoughts on this small PR?

@rittneje
Copy link
Collaborator

rittneje commented Jul 1, 2024

@ohaibbq Sorry, I really don't have the time for an in-depth review of this. (And I wouldn't describe this as a "small PR".)

case reflect.Ptr:
implReturnsPointer = true
case reflect.Interface:
implReturnsPointer = false
Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. How can you conclude whether it returns a pointer or not in this case?
  2. Saying it doesn't return a pointer and allowing that seems to contradict the error message in the default case.

Copy link
Author

Choose a reason for hiding this comment

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

You're right that I can't conclude if it returns a pointer or not in this case.

It does contradict the error message in the default case, but it was previously allowed in the initial implementation of user-defined functions #229.

Perhaps there is a better name for this variable.

return errors.New("SQlite aggregator doesn't have a Step() function")
return errors.New("SQLite aggregator doesn't have a Step() function")
}
err := ai.setupStepInterface(stepFn, &ai.stepArgConverters, &ai.stepVariadicConverter, implReturnsPointer, "Step()")
Copy link
Collaborator

@rittneje rittneje Jul 1, 2024

Choose a reason for hiding this comment

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

I don't entirely understand this call. The penultimate parameter to setupStepInterface is named isImplPointer, which actually means there is a method receiver? And that doesn't seem to have anything to do with implReturnsPointer.

Copy link
Author

Choose a reason for hiding this comment

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

I could rename the parameter to hasMethodReceiver.

I maintained the behavior from the original implementation, where we'd only skip the method receiver if the agg.Kind() == reflect.Pointer, but if we must always return a pointer, then there should always be a method receiver.

According to the reflect.Type docs:

// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver,
// and only exported methods are accessible.

When running sqlite3_test.go, I did not encounter any cases where implReturnsPointer would be false. I wonder if we should remove this branch of the logic.


//export inverseTrampoline
func inverseTrampoline(ctx *C.sqlite3_context, argc C.int, argv **C.sqlite3_value) {
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:int(argc):int(argc)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you explain what this is doing?

Copy link
Author

@ohaibbq ohaibbq Jul 1, 2024

Choose a reason for hiding this comment

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

I can try my best. For the step and inverse interfaces, SQLite passes us argc, the number of arguments to consume, and argv a double pointer to underlying sqlite3_values. A simple explanation of this code is that it initializes a slice of C.sqlite3_values with the length of argc.

In the initial implementation, we used the value
1 << 30 to represent the maximum possible length of this array.

#238 identified that this caused an overflow error. It was changed to use the maximum int32 size, divided by the size of nil *C.sqlite3_value, to limit its length without overflowing.

The crux seems to be that we cannot dynamically initialize an array with a length equal to argc. We must specify a constant size at compile time.

Once this array is initialized, we slice it down to the correct length as determined by argc.

I'm not familiar enough with Go / C to be sure of the exact performance implications of this, but
if you examine the pointer list prior to slicing, you can see that it contains 268,435,455 ((math.MaxInt31 - 1) / 8) elements. it seems like it would be much better if there were a way to only initialize it to argc.

@ohaibbq
Copy link
Author

ohaibbq commented Jul 1, 2024

@rittneje I appreciate the time you've spent on your initial review of this. Perhaps I can clean up the confusing interface of allowing implementations that return interface{} when we only expect pointers.

When testing registering an implementation that returns interface{}, registration understandably returns an error SQLite aggregator doesn't have a Step() function.

If you don't have much time to continue review, maybe we can defer to @mattn's review since you've gotten the chance to gloss over the code.

@ohaibbq
Copy link
Author

ohaibbq commented Dec 12, 2024

@mattn any updates on this?

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.

Support for custom window functions
3 participants