-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Support for streaming results for queries via new option (stream=true) #321
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
Conversation
Signed-off-by: Sagie Gur-Ari [email protected]
@sagiegurari thanks for submitting. Can you fill in the OCA as mentioned in CONTRIBUTING.md? |
done |
@sagiegurari I agree that the driver could really benefit from more JavaScript code! :) To lay a solid foundation down in this area, I suggest an architecture where we create a JavaScript equivalent for each of the driver's base classes. End users of the driver would only work with the JavaScript classes which would wrap the underlying C classes. This would allow us to add our JavaScript code in a more organized fashion. To show you what I'm talking about I've put together together this example: Keep in mind it's not fully baked, but it should get the idea across. In addition to demonstrating the architecture I'm suggesting, I've also added a connection request queuing feature for the Pool class. You can see how this works looking at pool.js and connection.js. I'd be really interested to hear what you think about this architecture. Could you see yourself adding the streaming feature there? I've created a resultSet (resultset.js) class which may be used in the execute method of the connection class. I would imagine you'd create a similar "ReadStream" class that would be used instead based on the options object... |
I couldn't agree more with the need and I see what you did in your fork. Basically it is similar approach in terms of extending oracledb like I did (take a look at another project of mine called simple-oracledb which I provide a lot more extensions) to provide additional logic in js. The wrapper approach leads to more maintenance work, theoretically more bugs and in some cases might not work. |
That's a fair point regarding the need to manually add whatever needs to be exposed to the JS layer. Though for simple pass throughs, this is quite simple so I'm not so worried about bugs there. When you say, "and in some cases might not work" what did you mean? Another issue I ran into with this approach is that properties like "pool.connectionsInUse" can't be mapped through very easily (at least I don't believe they can be as they are not like). Instead, getter methods would need to be introduced. On the other hand, this allows us to use JavaScript prototypes for all of the methods we'll be overriding. In the long term, as the code builds up, this could yield some memory benefits as there'll only be a single instance of the function on the prototype. Additionally, in one sweep, we could introduce a well laid out JS layer that folks could probably understand a bit easier and build out when needed. Food for thought! :) |
when I said might not work, is one scenario where in the native object it might call another public function which would result in bypassing the wrapper. |
A native object calling an instance method seems unlikely - and as you say, it may or may not be an issue with either implementation. Also, one of the enhancements I'd like to add to the driver is to make all async methods return promises over just undefined. This would mean that all methods (they're currently all async), including commit and release, would need to be overridden. |
|
I rounded out the JS layer today in the demo branch: I was able to figure out a solution for the the attribute values using Object.defineProperties. I think things are looking pretty good. @sagiegurari When your OCA is processed I'd like to have your streaming changes merged to see how it all hangs together. What do you think? |
I'm ok with doing changes whereever needed. |
Normally we'd extend classes in JS using either prototypes (vanilla JS) or util.inherits (Node.js). The idea with these solutions is to extend out one constructor function (sub) via another constructor function (super). However, the driver currently only exposes the base class: oracledb.Oracledb. In all other cases, we only get instances of classes. To do things via prototypes or util.inherits, the C layer would need to expose all base classes. That's definitely worth having a discussion about. Definitely! :) But in the mean time, I think this is the next best thing. Our current approaches are actually quite similar. We are both sorting a reference to the C layer method before adding a JS layer in front. I'm just doing it wholesale which would happen anyway when adding the Promises. |
not always. many times you augment an instance to provide more capabilities or change existing behavior. I believe that is what expressjs for example does with the nodejs request/response objects. |
@sagiegurari can you quantify 'not crazy'? We'd obviously be keeping C & JS in sync going forward. |
I was wrong! Seems the base object DOES expose all the other classes (Oracledb, Connection, Pool, ResultSet, and ILob)! I'll work on a new branch today that implements a better pattern using prototypes. |
Well, I tried, but I'm giving up on this approach (for now anyway). It was able to get this working with the base class. But the problem with this approach is that the other classes were not meant to be instantiated via "new" in the JavaScript layer. So, for example, when creating a pool you'd still have to call .createPool from the base class which would return a pool instance that wouldn't have any of the changes you made in the JS layer. :( |
@cjbj "not crazy" :) means that javascript gives you the power to modify any object. Wrapping objects leads to more maintenance it has no advantage (only disadvantages) compared to modifying the existing c++ instances.
|
I think i can give a better solution, in my pull request, i specifically changed the execute function and kept the old one. In connection.js: If I would do the same in the connection.js of this pull request as I did in simple-oracledb, I think it should help give a solution that would satisfy all? |
I spent some time yesterday exploring a standard prototypal inheritance pattern that didn't work out. Let me spend some cycles today on an extend/mixin pattern. Frankly, I could go either way. What I really want to see is a release where each base class is extended in a consistent manner so that we have a good foundation for JS developers to build out. |
👍 i'll be happy to give any input once you have something to look at. basically you can also modify the result set by modifying the connection.execute and wrapping the provided callback with your callback which would extend the c++ resultset with js level capabilities. |
Okay, here's a first pass at an extends implementation: https://github.com/dmcghan/node-oracledb/tree/javascript_extends_demo/lib Let me know if you what you think... |
better, few comments that I think can improve:
overall i'm much for this change 👍 and if you need me to do after your pull request, another one to push the streaming I'll do it. |
Cool! Regarding file names, I just want to be consistent. I started with a class=file approach. However, the extenders are not true classes (constructor functions invoked with the If we take a standard Node.js module approach, grouping things by related functionality, and just expose the interface (constructor or extend functions) then we'd end up with something like this: I do like that better, what about you? As for the empty functions, that was really just do demonstrate the approach we take when extending instances out. Keep in min that I do plan on building them all out so that they return Promises rather than just |
looks good. |
Thanks! And thanks for your suggestions! :) We would use es6 promises by default. If undefined, we could fall back on the es6 promise polyfill (a subset of rsvp.js): |
FYI: I've seen Node.js library authors leaning towards pinkie-promise as a promise polyfill. It manages the process of using es2105 promises when available and adds its own implementation only when not running in an es2015 environment. It's also very small, so it doesn't add a much extra weight to the library. |
lol, clever name! Thanks for the heads up, we'll have to look into both... |
Well this is a very good job, congrats @sagiegurari. |
@ecowden Thanks for the feedback! 👍 |
I just did a blog post that sums up the current status of this PR (ResultSet Streaming) and the promise support (that doesn't have it's own home yet - sorry Sagie!): |
The promise code changes look good to me. |
Thanks for taking a look Sagie! |
ok so I
Missing
Hope to setup some tests today/this week. |
Thanks @sagiegurari. I know @dmcghan wants to pound on it a bit. We will also work on tests too. |
To set expectations, Promises will be post 1.8.s |
Added a stress test of 500 queries using single connection with random streamNumRows value. |
@sagiegurari that's a good point to stop so we can work with a stable PR. Thanks! |
@sagiegurari do you feel it's OK to emit a |
I only emit the metadata and error events. if you look at the following doc, you will see that 'close' is an optional event. I can add code to emit that as well, but i'm pretty sure that error and end are doing the job just fine. |
Aren't you emitting My tests show that Can you check the following, which is crashing for me.
|
|
I recall the issue: because there is no explicit use of the connection it gets closed before all data is fetched. If I add a connection.release() to the end event, all is well. |
can you explain? is it a test error? |
@sagiegurari no problem with tests/stream.js. It was a problem with my standalone test closing the connection too early. In other news to keep you in the loop, we're getting this PR stable in our internal repo for stress testing etc. Dan's fixed an issue with pooling due to the pool connection.extend not passing oracledb. I'm still trying to find a better name than streamNumRows. We will likely move some functionality from connection.js to resultset-read-stream.js. I've done a some work on doc. Dan was wondering whether highWaterMark should be settable - this could be a post 1.8 topic to discuss. |
Why use an internal repo and not a branch per release that is open and visible to everyone? You will get better feedback early before you release it officially. |
@sagiegurari I think the discussion on a close/destroy method got lost. I think really important as without it we can't stop the stream, we can only pause it which keeps the resultset open (which blocks connection operations, like release). As there is no close method, I'm confused by this How would the closed property ever be set? |
That code is from when I did develop the close but removed it. Should remove that as well. |
I just ran a test and break does work. However, I think it should be possible to stop the stream without breaking the connection. A close method seems the most appropriate way to do this, no? |
I'm ok with that, I'll add it next week |
@sagiegurari we've frozen 1.8 ready for release testing, so hold off making changes for the moment. |
sure |
@sagiegurari thanks for your perseverance. We merged this to 1.8. @dmcghan moved more of the code into the new js file. At the last minute we decided to revert to using oracledb.maxRows for the getRows() size. We want to revisit this when #361 is looked at. |
Hello, EDIT : nevermind, it's working! 👍 |
This pull requests provides ability to stream query results but also provides 2 more additional items:
This could help a lot to this project and should provide a lot more contributions.