On update entity with version_id column (Optimistic Offline Lock) - mysql

I am facing the following problem while I am trying to update some values on an entity with version_id column.
Here is the snippet of code which I am using:
BASE = declarative_base()
class Job(BASE):
__tablename__ = 'jobs'
id = Column(String(36), primary_key=True, default=uuid.uuid4)
worker_id = Column(String(36), nullable=True)
status = Column(String(255), nullable=True)
timeout = Column(DateTime, nullable=False)
version_id = Column(String(36))
__mapper_args__ = {
'version_id_col': version_id,
'version_id_generator': lambda version: uuid.uuid4()
}
Approach #1 for updating a job entity:
query = session.query(Job)
.filter_by(id=job_id)
.update({'worker_id': worker_id, 'timeout': new_timeout)
SQLAlchemy generated the following query (mysql.log):
UPDATE jobs
SET worker_id='4a2350a1-f3ce-48f9-b29f-cc22e2e625fd',
timeout='2014-03-22 09:47:04.337521'
WHERE jobs.id ='84844301-446a-4912-85d7-92d9d462de5b'
Approach #2 for updating a job entity:
job = session.query(Job).filter_by(id=job_id).first();
job.worker_id = worker_id
job.timeout = new_timeout
session.add(job);
session.flush()
SQLAlchemy generated the following query (mysql.log):
UPDATE jobs
SET worker_id='1634939b-d3c6-462d-8254-d7f09640ba71',
timeout='2014-03-22 11:10:51.879507',
version_id='aa9d3f74-3e4a-4bae-af38-fe0530dbf9b7'
WHERE jobs.id = '3e6ab855-179e-4f4f-bf3a-847cf19b30fa'
AND jobs.version_id = '2c1288ba-ad27-444c-9f56-764df4898c52'
We can notice with Approach#1 the version_id column was never considered but in Approach#2 it was taken into consideration.
I thought even with Approach#1 sqlalchemy will be using the class_mapper.
Couldn't understand why is this difference of behavior.
Can someone please help me to understand why with Approach#1 the update call did not take into consideration the version_id column? OR am I missing anything?

As I could find any answer, went ahead and posted my question in 'sqlalchemy-dev' group.
Thanks to Michael Bayer for sparing his time to answer my question.
Thought would share the link to the answer here for benefiting others.
https://groups.google.com/forum/#!topic/sqlalchemy-devel/JUqlWKtRa4g

Related

joinedload and load_only but with filtering

I have two models with a simple FK relationship, Stock and Restriction (Restriction.stock_id FK to Stock).
class Restriction(Model):
__tablename__ = "restrictions"
id = db.Column(db.Integer, primary_key=True)
stock_id = FK("stocks.id", nullable=True)
name = db.Column(db.String(50), nullable=False)
class Stock(Model):
__tablename__ = "stocks"
id = db.Column(db.Integer, primary_key=True)
ticker = db.Column(db.String(50), nullable=False, index=True)
I would like to retrieve Restriction object and related Stock but only Stock's ticker (there are other fields omitted here). I can simply do this with:
from sqlalchemy.orm import *
my_query = Restriction.query.options(
joinedload(Restriction.stock).load_only(Stock.ticker)
)
r = my_query.first()
I get all columns for Restriction and only ticker for Stocks with above. I can see this in the SQL query run and also I can access r.stock.ticker and see no new queries run as it is loaded eagerly.
The problem is I cannot filter on stocks now, SQLAlchemy adds another FROM clause if I do my_query.filter(Stock.ticker == 'GME'). This means there is a cross product and it is not what I want.
On the other hand, I cannot load selected columns from relationship using join. ie.
Restriction.query.join(Restriction.stock).options(load_only(Restriction.stock))
does not seem to work with relationship property. How can I have a single filter and have it load selected columns from relationship table?
SQL I want run is:
SELECT restrictions.*, stocks.ticker
FROM restrictions LEFT OUTER JOIN stocks ON stocks.id = restrictions.stock_id
WHERE stocks.ticker = 'GME'
And I'd like to get a Restriction object back with its stock and stock's ticker. Is this possible?
joinedload basically should not be used with filter. You probably need to take contains_eager option.
from sqlalchemy.orm import *
my_query = Restriction.query.join(Restriction.stock).options(
contains_eager(Restriction.stock).load_only(Stock.ticker)
).filter(Stock.ticker == 'GME')
r = my_query.first()
Because you are joining using stock_id it will also be in the results as Stock.id beside Stock.ticker. But other fields would be omitted as you wish.
I have written short post about it recently if you are interested: https://jorzel.hashnode.dev/an-orm-can-bite-you

Optimizing hybrid_properties in SQLAlchemy

I have a piece of working code but it is very inefficient, instead of a single query with a join. I get one initial query, followed by one query per row in the response.
I have to following scenario:
class Job(Base, SerializeMixin, JobInterface):
__tablename__ = 'job_subjobs'
id = Column(Integer, primary_key=True, autoincrement=True)
group_id = Column(Integer, ForeignKey("job_groups.id"), nullable=False)
class Crash(Base, SerializeMixin):
__tablename__ = 'crashes'
id = Column(Integer, primary_key=True, autoincrement=True)
job_id = Column(Integer, ForeignKey("job_subjobs.id", ondelete='CASCADE'), nullable=False)
job = relationship('Job', backref='Crash')
#hybrid_property
def job_identifier(self):
return "{}:{}".format(self.job.group_id, self.job.id)
So given the above and I perform a query for all Crashes, It will perform one SELECT for all crashes. When I iterate and ask for job_identifier it will then do one separate SELECT for each crash.
self.session.query(Crash).all()
Is there someway i can create a #hybrid_property referencing a different table and have it JOIN from the beginning and preload the expression?
I've experimented with #xxx.expression without success. If all else fails I can add another foreign key in Crash table, but I would like to avoid changing current data structure if possible.
ended up using:
jobs = relationship('Job', backref='Crash', lazy='joined')

How to access autoincrement value after committing an object

I'm using flask-sqlalchemy. My class is:
class Game(db.Model):
userid = db.Column(db.Integer, primary_key=True)
gameid = db.Column(db.Integer, primary_key=True)
gameid is set to auto-increment in the database.
When I run this function:
#app.route("/requestgame")
def requestgame():
game = Game()
game.userid = session["userid"]
db.session.add(game)
db.session.commit()
session["gameid"] = game.gameid
return "gameid {}".format(game.gameid)
I get "ObjectDeletedError: Instance '' has been deleted, or its row is otherwise not present."
How can I get the gameid to return?
It looks like someone asked the same question about regular SQLAlchemy here:
SQLAlchemy Obtain Primary Key With Autoincrement Before Commit
but I have tried the recommended solution (calling session.flush()) and it doesn't seem to make any difference.

SQLALchemy filter_by() on a foreign key producting weird sql

I'm attempting to filter on a foreign key and none of the SO answers I've searched for have lent any results.
Where are my query statements.
testing = Comments\
.filter(Comments.post_id==post_id)
print(testing)
testing = Comments\
.query.join(Post, aliased=True)\
.filter(Comments.post_id==post_id)
print(testing)
Here's what my class definitions looks like
class Comments(db.Model):
comment_id = db.Column(db.Integer, primary_key=True)
post_id = db.Column(
db.Integer,
db.ForeignKey("Post.post_id"),
nullable=False)
class post(db.Model):
post_id = db.Column(db.Integer, primary_key=True)
Comments = db.relationship(
'Comments',
backref='Post',
lazy='dynamic')
The actual SQL queries which are being produced from the first and second case. They both have this weird :post_id_1 thing. In both cases I'm getting a null set back.
FROM "Comments"
WHERE "Comments".post_id = :post_id_1
FROM "Comments" JOIN "post" AS "post_1" ON "post_1".post_id = "Comments".post_id
WHERE "Comments".post_id = :post_id_1
If I do a simple
Select * from Comments where post_id = 1
in the mysql CLI I get a result set.
Your model definition is weird, the following part is not correctly indented:
Comments = db.relationship(
'Comments',
backref='Post',
lazy='dynamic')
Or maybe it's just a copy/paste issue (just to be sure).
What you call "weird :esc_id_1 thing" is in fact an named placeholder. They will be replaced by the real value when the SQL statement will be executed (this is mainly to avoid SQL injection, the driver is responsible to escape values).

Django query based on foreign key relationship

I have two models, a Project and an Action:
class Project(models.Model):
name = models.CharField("Project Name", max_length=200, unique = True)
class Action(models.Model):
name = models.CharField("Action Name", max_length=200)
project = models.ForeignKey(Project, blank=True, null=True, verbose_name="Project")
notes = models.TextField("Notes", blank=True)
complete = models.BooleanField(default=False, verbose_name="Complete?")
status = models.IntegerField("Action Status", choices = STATUS, default=0)
I need a query that returns all the Projects for which there are no actions with status < 2.
I tried:
Project.objects.filter(action__status__gt = 1)
But this returns all the Projects because in each Project, there are some actions with status 2 and some actions with status less than 2. Also, it repeated Projects in the resulting query. My current solution is below:
Project.objects.filter(action__status__gt =1).exclude(action__status__lt =2).annotate()
This collapses the repeating results and shows only actions with action statuses greater than 1. But is this the correct way to construct such a query? What if I wanted to return Projects with actions statuses greater than 1 OR Projects with no actions?
I might have misunderstood your requirement, but I think you can do that using annotations.
Project.objects.annotate(m = Min('action__status')).filter(Q(m = None) | Q(m__gt = 1))
The SQL generated is:
SELECT
"testapp_project"."id", "testapp_project"."name",
MIN("testapp_action"."status") AS "m"
FROM "testapp_project"
LEFT OUTER JOIN "testapp_action" ON ("testapp_project"."id" = "testapp_action"."project_id")
GROUP BY "testapp_project"."id", "testapp_project"."name"
HAVING(
MIN("testapp_action"."status") IS NULL
OR MIN("testapp_action"."status") > 1
)
Which is pretty self-explanatory.
Django's ORM is not capable of expressing this. You will need to use a raw query in order to perform this.