Skip to content

Add support for RETURNING in SQLAlchemy dialect #363

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

Merged
merged 1 commit into from
Jul 29, 2020
Merged

Conversation

chaudum
Copy link
Contributor

@chaudum chaudum commented Jul 27, 2020

Summary of the changes / Why this is an improvement

Example:

import random

from sqlalchemy import create_engine, inspect, func, Column, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import FetchedValue

engine = create_engine(
    "crate://",
    connect_args={
        "username": "crate",
    },
)
Session = sessionmaker(bind=engine)
Base = declarative_base(bind=engine)

class Log(Base):
    __tablename__ = "logs"
    __table_args__ = {"schema": "doc"}
    __mapper_args__ = {"exclude_properties": ["id"]}

    id = Column("_id", String, server_default=FetchedValue(), primary_key=True)
    ts = Column(DateTime, server_default=func.current_timestamp())
    level = Column(String)
    message = Column(String)

if __name__ == "__main__":
    o = Log(level="info", message="Hello World")
    session = Session()
    session.add(o)
    session.commit()

    print(f"{o.id=}, {o.ts=}, {o.level=}, {o.message=}")

@chaudum chaudum force-pushed the h/support-returning branch from ca19003 to aa8d274 Compare July 28, 2020 07:31
@chaudum chaudum marked this pull request as ready for review July 28, 2020 07:31
@chaudum chaudum requested a review from a team July 28, 2020 07:31
@@ -165,6 +165,7 @@ class CrateDialect(default.DefaultDialect):
type_compiler = CrateTypeCompiler
supports_native_boolean = True
colspecs = colspecs
implicit_returning = True
Copy link
Member

Choose a reason for hiding this comment

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

Will this still work with older CrateDB versions which don't have RETURNING ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, as long as you don't add a column with server_default=FetchedValue() which requires RETURNING`, e.g:

class LogOld(Base):
    __tablename__ = "logs_old"
    __table_args__ = {"schema": "doc"}

    ts = Column(DateTime, primary_key=True)
    level = Column(String, primary_key=True)
    message = Column(String, primary_key=True)

With the schema:

create table logs_old (ts timestamp with time zone, level text, message text)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If implicit_returning is False, it would never add the RETURNING clause to the INSERT statement.

I guess what you mean is that we should not change behaviour for old versions. ( it will be a different exception).

Copy link
Member

@mfussenegger mfussenegger Jul 28, 2020

Choose a reason for hiding this comment

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

Just wanted to make sure that it doesn't add a RETURNING to other statements that previously worked

Could you add a changes entry?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just wanted to make sure that it doesn't add a RETURNING to other statements that previously worked

I will test a bit more.

Could you add a changes entry?

Sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found an issue when running one of our test suites, where an INSERT statement would add a RETURNING clause. However cannot reproduce it when the model is used outside of the test suite.

Copy link
Contributor Author

@chaudum chaudum Jul 28, 2020

Choose a reason for hiding this comment

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

Ok, so as soon as one column of the PK has a server side generated value, the SQLAlchemy ORM will append a RETURNING (in order to get the full PK for the model instance).

Example that works at the moment, but would fail with this change:

from sqlalchemy import create_engine, Column, String, DateTime, text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


engine = create_engine(
    "crate://",
    connect_args={
        "username": "crate",
        "servers": "localhost:4200",
    },
)

Session = sessionmaker(bind=engine)
Base = declarative_base(bind=Session)


class Event(Base):
    __tablename__ = "event"
    __table_args__ = {"schema": "doc"}

    ts = Column("ts", DateTime, primary_key=True, server_default=text("current_timestamp"))
    event = Column("event", String, primary_key=True)


if __name__ == "__main__":
    session = Session()
    o = Event(event="status")
    session.add(o)
    session.commit()
    print(o)
CREATE TABLE doc.event (
  ts TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  event TEXT
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure if this is a show stopper, because having the primary key in the model but not in the database is an awful workaround for not being able to use the FetchedValue() for _id. What do you think?

Copy link
Member

@mfussenegger mfussenegger Jul 28, 2020

Choose a reason for hiding this comment

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

Can users override the implicit_returning value? If so, I think a (breaking?) changes entry that explains the impact of the change and how to work around it if necessary should be good enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you can override it when creating the engine: create_engine('crate://',..., implicit_returning=False).

@mfussenegger mfussenegger requested a review from autophagy July 28, 2020 08:32
@chaudum
Copy link
Contributor Author

chaudum commented Jul 28, 2020

Not sure why Python 3.5 keeps failing with ##[error] raise SystemError('Failed to start Crate instance in time.')

This will enable the usage of `FetchedValue()` as server side default
for primary keys in the declarative model.

```python
import random

from sqlalchemy import create_engine, inspect, func, Column, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import FetchedValue

engine = create_engine(
    "crate://",
    connect_args={
        "username": "crate",
    },
)
Session = sessionmaker(bind=engine)
Base = declarative_base(bind=engine)

class Log(Base):
    __tablename__ = "logs"
    __table_args__ = {"schema": "doc"}
    __mapper_args__ = {"exclude_properties": ["id"]}

    id = Column("_id", String, server_default=FetchedValue(), primary_key=True)
    ts = Column(DateTime, server_default=func.current_timestamp())
    level = Column(String)
    message = Column(String)

if __name__ == "__main__":
    o = Log(level="info", message="Hello World")
    session = Session()
    session.add(o)
    session.commit()

    print(f"{o.id=}, {o.ts=}, {o.level=}, {o.message=}")
```
@chaudum chaudum force-pushed the h/support-returning branch from aa8d274 to e0d18f1 Compare July 28, 2020 13:25
@chaudum chaudum merged commit cf02fd5 into master Jul 29, 2020
@chaudum chaudum deleted the h/support-returning branch July 29, 2020 07:40
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.

2 participants