Skip to content

Database abstraction / using an ORM #74

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

Closed
ssweber opened this issue Feb 16, 2023 · 22 comments
Closed

Database abstraction / using an ORM #74

ssweber opened this issue Feb 16, 2023 · 22 comments

Comments

@ssweber
Copy link
Collaborator

ssweber commented Feb 16, 2023

I was thinking about this the other day. What are you thoughts on using an ORM like Pony to handle the backend?

This is all new to me, but I imagine first switch to using their execute() function, and then work through replacing functionality with their higher level functions.

————————————————————
My next big update will be some database abstraction so that I can start
adding other database engines :)

Originally posted by @PySimpleSQL in #70 (comment)

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 16, 2023

Hah - posted this, then saw you already created an abstracted_database branch with a ton of changes. :)

@PySimpleSQL
Copy link
Owner

PySimpleSQL commented Feb 16, 2023 via email

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 16, 2023

BTW - I tested the sqlite version of your abstracted_database on my work program, and it looks like everything was working.

@ssweber ssweber closed this as completed Feb 16, 2023
@PySimpleSQL
Copy link
Owner

PySimpleSQL commented Feb 16, 2023 via email

@PySimpleSQL
Copy link
Owner

PySimpleSQL commented Feb 17, 2023 via email

@PySimpleSQL
Copy link
Owner

With most of the database abstraction done (duplicate still needs done), I wanted to have some discussion on some sane default behavior. Currently, after inserting a new record:

  • If the user navigates to a new record or creates another new record, they will be prompted to save. On a "No" selection, the virtual record is purged.
    Is this a good default behavior, or should the record remain and marked as virtual?

  • I think I may turn the SAVE_* constants to bitmasks so that I can add a flag to show the generic messsage. This would be to prevent back to back messages, such as is the case currently when a query fails; The user is shown the query error message, then after that message the more generic message about problems encountered during saving.

  • Regarding the marking of virtual records on Table selectors - should this be a user-definable option? I kind of like the current implementation, though it's accomplished in a rather sneaky way by inserting an extra column in the table - which could lead to some confusion if a user had to do some low-level stuff with the table rows. Maybe it's just a documentation issue?

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 21, 2023

  1. If there is a requery/requery_dependents, that virtual row would be lost, correct? If so, then yes, if user navigates away and says No to saving the virtual row, I think it should purged.
  2. cool!
  3. I like how you put in the star indicator. I think it’s good for now, unless you want to put a toggle to disable it, ie virtual_record_indicator = False .

@PySimpleSQL
Copy link
Owner

Thanks for the feedback - this is what I was thinking as well but its always nice to get opinions for others that use it

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 21, 2023

For duplicate, is this all that would be needed to make it compatible with all three?

4dacca8

@PySimpleSQL
Copy link
Owner

Not quite - we will probably have to add a duplicate method to the drivers themselves - Posgres in particular must have the table names in double quotes. Plus, this will keep all direct queries inside the drivers

PySimpleSQL added a commit that referenced this issue Feb 21, 2023
Slight rework of messages, as bitmasking permits multiple results
PySimpleSQL added a commit that referenced this issue Feb 21, 2023
Default driver duplicate_record() method fleshed out.  SHould work fine with SQLite and MySQL.  Will override for the Postgres version
PySimpleSQL added a commit that referenced this issue Feb 21, 2023
I'm thinking since most of the differences between drivers has more to do wth quoting than anything else, perhaps the driver system can be simplified by having most of the query strings in the abstract class, with calls to the derived class to quote things properly
PySimpleSQL added a commit that referenced this issue Feb 21, 2023
- improved SQLDriver so that more default methods will work for various databases.  This was accomplished by adding quote_tabe(), quote_column() and quote_value() methods, as the largest difference between databases is between how the query strings are formatted.
- moved delete_record() to be handled by the driver
- started working on cleanup and documentation of the abstracted database concept
PySimpleSQL added a commit that referenced this issue Feb 21, 2023
…l...

get the next pk from the database.  This still isn't perfect, as there are still 2 issues:
- We still aren't querying the sequencer directly
- the insert portion of the upsert query currently manually creates the primary key - if it didn't, then the insert would always go through, defeating the purpose of the upsert.

Moving forward, there are two options:
1- continue just adding record primary keys manually, with the possibility of manually updating the sequencer afterwards?
2- have separate insert and update queries.  This would definitely involve also having accurate primary key synchronization with the sequencer.
PySimpleSQL added a commit that referenced this issue Feb 21, 2023
small commenting improvements
@PySimpleSQL
Copy link
Owner

Huge improvements to this today. Should fix a ton of little collision issues

  • The SQLDriver abstract class got quite a bit of improvements to reduce the amount of work required to write a new driver
  • Got rid of the UPSERT in favor of separate INSERT and UPDATE methods. Additionally, these now return a ResultSet object, so inserted keys can be communicated back to the caller and the selector updated after the insert/update
  • SQLite and MySQL use autoincrement to avoid collisions. PostgreSQL used nextval() to reserve a primary key, which is then manually inserted back into the database. This also avoids collisions
  • Due to the weirdness of PostgreSQL and sequences, made an option (default True) to sync the sequences in the database with the max primary key. This should eliminate collisions from manual insertions outside of pysimplesql

Initial tests are very promising. I'd be interested in hearing how your parent/child tests go with this @ssweber

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 22, 2023

I saw your flurry of activity. I tried testing, but ran into this bug:

python_7gUct4V0S8.mp4

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 22, 2023

The file I used for that is: here.

I tested with abstracted_database branch, current after your changes.

I also worked to unify the saving mechanism... Form.save_records, Form.prompt_save and Query.prompt_save all use Form.save_records in this diff: https://github.com/PySimpleSQL/pysimplesql/compare/abstracted_database...ssweber:pysimplesql:ab_save_record?diff=unified

Once we fix the above bug, I'll re-integrate this code, since it's not a clean merge.

@PySimpleSQL
Copy link
Owner

PySimpleSQL commented Feb 22, 2023 via email

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 23, 2023

Btw - I branched from This branch, so it’s just been in the commits from today. If that helps narrow it down. Might take a look at it later and narrow it down to the commit that introduces it.

@PySimpleSQL
Copy link
Owner

I did a quick attempt at a fix from my phone. I'm not able to test myself, we have some differences still (my Form no longer supports sql_commands, as it's moved to the driver)

I'll look more tomorrow when I have a computer in front of me

@ssweber
Copy link
Collaborator Author

ssweber commented Feb 23, 2023

Cool I’ll test in the morning.

I posted the wrong form - this one uses the sqldriver.

https://github.com/ssweber/pysimplesql-examples/blob/abstract_database/tests/parent%20child%20grandchild.py

@PySimpleSQL
Copy link
Owner

Ok, I have the issue narrowed down. For some reason, cursor.lastrowid is always returning 2 in the driver. Going to look at it now.

@PySimpleSQL
Copy link
Owner

Just commenting with my research so far:
sqlite documentation states that cursor.lastrowid will return the the primary key from the the affected row on an INSERT or REPLACE query only, and None in all other instances. However, i'm showing that it returns 2 instead of None in other instances, and that is the issue we are having. Going to continue researching to find a real fix before hacking around the issue

@PySimpleSQL
Copy link
Owner

Ok, fix is pushed.

In the end, I did have to kind of hack around the issue. With SQLite, the cursor.lastrowid is not operating as it should per documentation. After countless tests, not once could I get it to return None on an UPDATE query - which the docs clearly state should happen.

In the end, for SQLDriver.save_record() I just manually set the ResultSet.lastrowid = None, as in the end it really doesn't matter. The caller already knows the pk, as it was passed into save_records() - so this will be more of a documentation issue that ResultSet.lastrowid will only be set when SQLDriver.insert_record() is run, or on any INSERT query executed with SQLDriver.execute(). This will at least be consistent across the databases on the pysimplesql end

PySimpleSQL added a commit that referenced this issue Feb 23, 2023
Adds at least some minimum documentation on ResultSet.lastrowid
@ssweber
Copy link
Collaborator Author

ssweber commented Feb 23, 2023

Your fix worked :). Thank you!

Maybe you were bumping into where pythons wrapper diverges from direct SQLite?

lastrowid:
Read-only attribute that provides the row id of the last inserted row. It is only updated after successful INSERT or REPLACE statements…
…if the insertion failed, the value of lastrowid is left unchanged. The initial value of lastrowid is None.

https://docs.python.org/3/library/sqlite3.html

PySimpleSQL added a commit that referenced this issue Feb 23, 2023
Need to start cleaning this up soon, I can see a new release coming
PySimpleSQL pushed a commit that referenced this issue Feb 24, 2023
Cleaning up and documentation improvements
PySimpleSQL pushed a commit that referenced this issue Feb 24, 2023
Cleaning up and documentation improvements (had a misdirected commit comment accidentally end up in the readme)
PySimpleSQL pushed a commit that referenced this issue Feb 24, 2023
I've spent way too much time on this.  I can't for the life of me figure out why the asterisks are appearing AFTER the column field, and not before as they are clearly defined.  I'm thinking this has to be a PySimpleGUI bug of some sort.  The sg.Text() element is defined in the layout before the actual element, but displays after the element in the GUI.  I have purposely set the visibility to Tue on creation and you can see a very quick blip on loading where the "marker" appears before the sg.element - but everythin after shows it after the element
@PySimpleSQL
Copy link
Owner

This is working quite well as a concept - I'm going to close this now

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

No branches or pull requests

2 participants