Jump to content

[CR] SQLite API


Recommended Posts

Found a bug while optimizing mine that affects yours as well.

If you insert a blank string it'll probably be bound as a NULL.

post-7834-0-65597000-1290697920_thumb.pn

It can be fixed if the empty string passed to sqlite3_bind_text is null terminated (I'm not sure if this is a bug with sqlite or a undocumented feature to allow writing nulls with string binding).

post-7834-0-07385600-1290698196_thumb.pn

Inserting as a string instead of a byte array also fixes it since LabVIEW will null terminate all strings passed to the CLN.

Edited by Matt W
Link to comment

Found a bug will optimizing mine that affects yours as well.

If you insert a blank string it'll probably be bound as a NULL.

post-7834-0-65597000-1290697920_thumb.pn

It can be fixed if the empty string passed to sqlite3_bind_text is null terminated (I'm not sure if this is a bug with sqlite or a undocumented feature to allow writing nulls with string binding).

post-7834-0-07385600-1290698196_thumb.pn

Inserting as a string instead of a byte array also fixes it since LabVIEW will null terminate all strings passed to the CLN.

I get error code 19 with your 1st snippet

Abort due to contraint violation

SQLite_Error.vi:2>SQLite_Bind Execute.vi>SQLite_Insert Table.vi:2>Untitled 1

If the field is not declared with NOT NULL it succeeds.

Link to comment

I get error code 19 with your 1st snippet

Abort due to contraint violation

SQLite_Error.vi:2>SQLite_Bind Execute.vi>SQLite_Insert Table.vi:2>Untitled 1

If the field is not declared with NOT NULL it succeeds.

Exactly, without the fix the empty string is bound as a NULL (unless the pointer passed to the dll happens to point to a \00), which fails the constraint. With the fix it get's bound as an empty string and passes the constraint.

I figured out the 2x size 32 bit example slowed down. I had left the speed test front panel open, without the front panel being open it doesn't slow down.

Link to comment

Exactly, without the fix the empty string is bound as a NULL (unless the pointer passed to the dll happens to point to a \00), which fails the constraint. With the fix it get's bound as an empty string and passes the constraint.

I figured out the 2x size 32 bit example slowed down. I had left the speed test front panel open, without the front panel being open it doesn't slow down.

I thought about this a while back.

Labview has no concept of "NULL" string. We can neither create it nor check for it. If we put a string control / constant down, we can only have an empty string (and any of our VIs that accept a string will have a string control).

So the choice becomes do we allow a write to a NOT NULL field to always succeed (which is what will happen with your suggestion) or do we define an empty string in LV as being the equivalent to a NULL string. I think the latter is more useful.

I figured out the 2x size 32 bit example slowed down. I had left the speed test front panel open, without the front panel being open it doesn't slow down.

Good job I cannot take back the rep point eh? biggrin.gif

Edited by ShaunR
  • Like 1
Link to comment

I thought about this a while back.

Labview has no concept of "NULL" string. We can neither create it nor check for it. If we put a string control / constant down, we can only have an empty string (and any of our VIs that accept a string will have a string control).

So the choice becomes do we allow a write to a NOT NULL field to always succeed (which is what will happen with your suggestion) or do we define an empty string in LV as being the equivalent to a NULL string. I think the latter is more useful.

[/size][/color]

Good job I cannot take back the rep point eh? biggrin.gif

I have a variant interface for differentiating nulls and empty strings when needed (which I need in my case). But with a pure string interface, making an empty string null is more useful.

[edit] this seems to be wrong

If you want an empty string to be written as NULL then you need to make sure the value passed doesn't point to null (just replace the 0 in my fix with any other number).

[/edit]

It seems an empty arrays are pointer to 0 which is why SQLite makes it null, not that it doesn't point to a /00 character.So you don't need to fix your current version to keep empty string as null.

Edited by Matt W
Link to comment

I have a variant interface for differentiating nulls and empty strings when needed (which I need in my case). But with a pure string interface, making an empty string null is more useful.

[edit] this seems to be wrong

If you want an empty string to be written as NULL then you need to make sure the value passed doesn't point to null (just replace the 0 in my fix with any other number).

[/edit]

It seems an empty arrays are pointer to 0 which is why SQLite makes it null, not that it doesn't point to a /00 character.So you don't need to fix your current version to keep empty string as null.

An empty array is slightly different.

Since I use self indexing for loops, the bind never gets executed. However. I've just noticed that the classic problem also occurs. The sql ref and the DB ref are passed as "0" to the Bind Clear meaning they never get freed. Whilst his is obviously undesirable, I would have expected SQLite to return a "Misuse" error. It doesn't angry.gif Instead error 1097 occurs which isn't good (possible crash under the right conditions). I will put some defensive code around this

So inadvertently you have uncovered a bug, although not the original one wink.gif

Edited by ShaunR
Link to comment

An empty array is slightly different.

Since I use self indexing for loops, the bind never gets executed. However. I've just noticed that the classic problem also occurs. The sql ref and the DB ref are passed as "0" to the Bind Clear meaning they never get freed. Whilst his is obviously undesirable, I would have expected SQLite to return a "Misuse" error. It doesn't angry.gif Instead error 1097 occurs which isn't good (possible crash under the right conditions). I will put some defensive code around this

So inadvertently you have uncovered a bug, although not the original one wink.gif

Just replace the terminals with shift registers (generally a good rule with for loops anyway).

But I was talking about the byte array from the string that gets passed to the bind string (it seems that empty arrays are passed as pointers to 0 in CLNs).

Is there a reason you're clearing bindings before finalizing the statement. It's my understanding the clear just sets all parameters to null, and since you're closing it doesn't matter what the bindings are.

Link to comment

Is there a reason you're clearing bindings before finalizing the statement. It's my understanding the clear just sets all parameters to null, and since you're closing it doesn't matter what the bindings are.

Yes and no biggrin.gif

I like the encapsulation so that If I decide to expose the "Bind Execute" into the Low Level palette, then the user would not have to worry about it. I'm not sure (now) if bindings are persistent or not across opening and closing the DB (I originally thought it was persistent) as it is not stated anywhere and when I checked I couldn't discern any difference in performance. So just erring on the side of caution really.

When I run out of things to do, I might look at it again (if I remember wink.gif).

Have you noticed any improvement in performance between this version (1.2.1) and version 1.1?

Edited by ShaunR
Link to comment

Yes and no biggrin.gif

I like the encapsulation so that If I decide to expose the "Bind Execute" into the Low Level palette, then the user would not have to worry about it. I'm not sure (now) if bindings are persistent or not across opening and closing the DB (I originally thought it was persistent) as it is not stated anywhere and when I checked I couldn't discern any difference in performance. So just erring on the side of caution really.

When I run out of things to do, I might look at it again (if I remember wink.gif).

Have you noticed any improvement in performance between this version (1.2.1) and version 1.1?

Finalizing should destroy all the bindings (as well as the statement). And since you have to finalize before closing they shouldn't be persistent.

Oh I think you missed an error wire in the win32 part of fetch record.

Speed wise using the current version of yours and the current version of mine (which I've made a a couple speed improvements to since the last time I compared) are very close. Your winning in inserts and If you modify fetch all so you only check the column count once per statement instead of per row you should win dumps as well. For me to to be equal speed wise, as far as I can tell, I'd need to break features that I use.

On XP LV2010 Both with sync off

10000 Records

Yours

Insert 57.36 Dump 59.39

Mine

Insert 59.21 Dump 57.1

100000 Records

Yours

Insert 587.89 Dump 583.53

Mine

Insert 609.81 Dump 582.05

Link to comment

Seems that I do better on my Win7 machine at home.

On Win7 LV2010 32bit

10000 Records

Yours

Insert 40.95 Dump 37.14

Mine

Insert 35.57 Dump 36.87

100000 Records

Yours

Insert 405.19 Dump 371.07

Mine

Insert 351.46 Dump 376.54

On Win7 LV2010 64bit

10000 Records

Yours

Insert 39.11 Dump 41.59

Mine

Insert 29.88 Dump 33.94

100000 Records

Yours

Insert 377.77 Dump 471.24

Mine

Insert 289.93 Dump 397.06

Link to comment

Excellent. So my "improvements" are indeed improvements.yes.gif

I think we are getting to the stage where implementation is becoming the major difference. Obviously my version uses a much deeper nesting since I prefer a modular decomposition (and have abstracted further) as opposed to (say) putting many DLL calls in one VI - which would save the sub-vi overheads. But for the sake of performance vs maintenance that is acceptable (to me at least). I would also expect you to be squeezing a little more by using the 2010 in-lining feature (if you are not, then you should be) which is unavailable in 2009. But I take note of your suggestion to promote the get_column_count which (in theory) should skim a ms or two of the time (does't seem to make any noticeable difference on my machine though).

Link to comment

Excellent. So my "improvements" are indeed improvements.yes.gif

I think we are getting to the stage where implementation is becoming the major difference. Obviously my version uses a much deeper nesting since I prefer a modular decomposition (and have abstracted further) as opposed to (say) putting many DLL calls in one VI - which would save the sub-vi overheads. But for the sake of performance vs maintenance that is acceptable (to me at least). I would also expect you to be squeezing a little more by using the 2010 in-lining feature (if you are not, then you should be) which is unavailable in 2009. But I take note of your suggestion to promote the get_column_count which (in theory) should skim a ms or two of the time (does't seem to make any noticeable difference on my machine though).

Mine is heavily dependent on inlining for speed, you're probably much faster in LV2009 due to that. If I needed LV2009 support I'd probably switch to subroutines. VI nesting wise we're pretty similar (if we ignore the higher level abstractions on yours), I have only three VIs where I have more than one DLL call (one to copy a query string to a pointer, the other is handle strings with /00, the last is to read a column based on it's type). But I avoid the speed lose due to inlining. I added a repeated query interface to mine (I use it to run stuff like mass inserts) and In the benchmark I gain about 10% on the insert due to inlining (even though it's only called once). LabVIEW cuts of all the dead call for storing the results of the repeated query (since I don't wiring the result data on the insert). If I wire the results to something I loose my 10% gain. The dead code elimination and inlining makes writing a performant interface much easier since I don't always to write multiple version of the code for different requirements (which also simplifies the interface).

If I modify your fetch all on LV2010 64 on win7 I gain about 13 ms on a select with 100000 records.

A few more suggestions on your version.

Drop the high priority execution setting, typically disk IO is considered low priority (starving data collection would be bad).

You should support for sqlite3_errmsg in your error handler, since that'll give a much explanation of the error (stuff like table names or which command is misspelled).

You'll probably want update to use binding, since currently you can't update to a blob value.

Link to comment

If I modify your fetch all on LV2010 64 on win7 I gain about 13 ms on a select with 100000 records.

Thats about 3% which is hardly worth the effort biggrin.gif (although I'm not sure what you mean by modifying.....in-lining?) If I in-line the VIs I get exactly the same performance for insertions as you. But slightly slower on select (only gaining about 5ms in the 10,000 test)

A few more suggestions on your version.

Drop the high priority execution setting, typically disk IO is considered low priority (starving data collection would be bad).

That won't happen. I use a high priority but in a different execution system and therefore force LV to run the queries in a different thread from the users application (assuming the user isn't using the same execution system of course). It basically forces a high priority thread rather than VIs which should mean it gets a higher priority on the scheduler. On my machine I always run with the maximum number of threads (~200) since a lot of my systems use asynchronous tasks at various priorities. This is the way things like VISA work Although I did notice it is set to "Standard" and should be set to "Other 1" (not quite sure how that got changed).

You should support for sqlite3_errmsg in your error handler, since that'll give a much explanation of the error (stuff like table names or which command is misspelled).

I did look at it. But found that I needed to check the error every dll call and extract the string if need be. So I went for passing the error code up the chain and converting it at the end.

You'll probably want update to use binding, since currently you can't update to a blob value.

Indeed. Missed that one.

Link to comment

Thats about 3% which is hardly worth the effort biggrin.gif (although I'm not sure what you mean by modifying.....in-lining?) If I in-line the VIs I get exactly the same performance for insertions as you. But slightly slower on select (only gaining about 5ms in the 10,000 test)

I meant move the column count out of the while loop.

post-7834-0-67585500-1290899712_thumb.pn

That won't happen. I use a high priority but in a different execution system and therefore force LV to run the queries in a different thread from the users application (assuming the user isn't using the same execution system of course). It basically forces a high priority thread rather than VIs which should mean it gets a higher priority on the scheduler. On my machine I always run with the maximum number of threads (~200) since a lot of my systems use asynchronous tasks at various priorities. This is the way things like VISA work Although I did notice it is set to "Standard" and should be set to "Other 1" (not quite sure how that got changed).

You might want to put a note in the documentation on which execution system you're using. I've just been using the same as callers in mine. I haven't really experimented with tuning priorities in LabVIEW.

I did look at it. But found that I needed to check the error every dll call and extract the string if need be. So I went for passing the error code up the chain and converting it at the end.

I didn't find it that to be much of a problem (you would just need to add it to your SQLite_Error VI, and maybe call it a bit more often). It helps a lot with debugging, of course I'm working with raw SQL strings far more often than you are.

Link to comment

I meant move the column count out of the while loop.

post-7834-0-67585500-1290899712_thumb.pn

IC. Yes I tried that and didn't notice much of a change, but then again 3% on 40ms is only about 1ms which is in the noise level. I would have expected more, But it seems DLL call overhead is virtually non existent when set to a subroutine.

You might want to put a note in the documentation on which execution system you're using. I've just been using the same as callers in mine. I haven't really experimented with tuning priorities in LabVIEW.

IF I ever write a manual. I will biggrin.gif

If you don't use execution systems and/or priorities, then you are limited to 4 threads (+1 for the UI). I don't think most people worry about it (LV is very good t making things appear to be very multi-threaded), but with very IO oriented asynchronous designs,it improves performance immensely (if used correctly). ThreadConfg.vi is my favourite VI tongue.gif

I didn't find it that to be much of a problem (you would just need to add it to your SQLite_Error VI, and maybe call it a bit more often). It helps a lot with debugging, of course I'm working with raw SQL strings far more often than you are.

I'll keep it in mind for now.

Link to comment

a little suggestion.

I think it could be usefull to add an Order By array input in the select Vi.

You can use the "Order BY" in the where clause.

e.g

Note that you have to have a where statement of some description so you can use RowID>0 to return all rows in a table if you have no "Where" constraint.

Link to comment

Hi,

I am trying to use the sqlite functions in an executable. DO I need to add something in the installer for this to work successfully?

Please guide me.

Thanks,

Subhasis

It is looking for labview.exe which it can not find, and so the SQLiteRread Blob could not load.

Hmmm. This could potentially be a nasty one

Open the SQLite project and Find "SQLite Read Blob.vi"

Open the "MoveBlock" call library node and change it from "Labview.exe" to "lvrt"

Click "OK"

If it starts to search for the dll, Help it by browsing to your "/program files / national instruments / shared / Labview Run-Time / [your version] directory and selecting "lvrt.dll".

Save the vi and the project.

You should now be able to open your project and build the app. It may ask you again to find the lvrt.dll. If it does, so the same "browse" again as the above.

I can put in a switch to detect the change between run-time and dev, but if its not automagically picking up the run-time dll, this will cause a huge headache in multiple version systems.

I'll look more closely into this, but the above should enable you to build your app until I find a better solution.

Edited by ShaunR
Link to comment

OK.

Definitive answer. Change the "SQLite Read Blob.vi" moveblock call to "LabVIEW" (note the case...it matters!) instead of "labview.exe" or "lvrt"

Let me know if it fixes your issue and I'll add it to the API library.

Edited by ShaunR
Link to comment
Guest
This topic is now closed to further replies.
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.