Skip to content
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

[Feature Proposal]: async support #1031

Open
evan0greenup opened this issue Jun 25, 2024 · 1 comment
Open

[Feature Proposal]: async support #1031

evan0greenup opened this issue Jun 25, 2024 · 1 comment

Comments

@evan0greenup
Copy link

According to https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html,

It For a SQL DB implementation, it require relevant asyncio driver to make SQLAlchemy support this database in asyncio mode.

It would be awesome to add asyncio feature in duckdb_engine.

@sherpya
Copy link

sherpya commented Jul 31, 2024

currenty testing with this code:

from typing import Type

from duckdb_engine import Dialect
from sqlalchemy import URL, pool
from sqlalchemy.dialects import registry  # noqa


class AsyncDuckDB(Dialect):
    driver = 'aioduckdb'
    supports_statement_cache = True

    is_async = True

    @classmethod
    def get_pool_class(cls, url: URL) -> Type[pool.Pool]:
        if url.database == ':memory:':
            return pool.NullPool
        else:
            return pool.StaticPool


registry.register('duckdb.aioduckdb', 'engine.aioduckdb', 'AsyncDuckDB')

it is safe to set supports_statement_cache to True with this driver?

here the example:

#!/usr/bin/env python3
import asyncio

from sqlalchemy import Column, Integer, Sequence, String, Select
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import DeclarativeBase

from engine import aioduckdb  # noqa


class Base(AsyncAttrs, DeclarativeBase):
    pass


class FakeModel(Base):
    __tablename__ = 'fake'

    id = Column(Integer, Sequence('fakemodel_id_sequence'), primary_key=True)
    name = Column(String)

    def __str__(self):
        return f'FakeModel(id={self.id}, name={self.name})'


async def main():
    engine = create_async_engine('duckdb+aioduckdb:///:memory:', echo=True)
    # engine = create_async_engine('duckdb+aioduckdb:///test.db', echo=True)

    async_session = async_sessionmaker(engine, expire_on_commit=False)

    async with async_session() as session:
        async with session.begin():
            conn = await session.connection()
            await conn.run_sync(Base.metadata.drop_all)
            await conn.run_sync(Base.metadata.create_all)

            session.add(FakeModel(name='Frank'))
            session.add(FakeModel(name='Joe'))

            for result in await session.scalars(Select(FakeModel)):
                print(result)

            for result in await session.scalars(Select(FakeModel)):
                print(result)

            result = await session.stream(Select(FakeModel))

            async for e in result.scalars():
                print(e)

        await session.commit()


if __name__ == '__main__':
    asyncio.run(main())

output:

2024-07-31 15:27:14,401 INFO sqlalchemy.engine.Engine select current_schema()
2024-07-31 15:27:14,401 INFO sqlalchemy.engine.Engine [raw sql] ()
2024-07-31 15:27:14,402 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-07-31 15:27:14,402 INFO sqlalchemy.engine.Engine 
            SELECT oid, table_name
            FROM (
                SELECT table_oid AS oid, table_name,              database_name, schema_name FROM duckdb_tables()
                UNION ALL BY NAME
                SELECT view_oid AS oid , view_name AS table_name, database_name, schema_name FROM duckdb_views()
            )
            WHERE schema_name NOT LIKE 'pg\_%' ESCAPE '\'
            AND table_name = $1

2024-07-31 15:27:14,403 INFO sqlalchemy.engine.Engine [generated in 0.00011s] ('fake',)
2024-07-31 15:27:14,434 INFO sqlalchemy.engine.Engine SELECT pg_catalog.pg_class.relname 
FROM pg_catalog.pg_class JOIN pg_catalog.pg_namespace ON pg_catalog.pg_namespace.oid = pg_catalog.pg_class.relnamespace 
WHERE pg_catalog.pg_class.relkind = $1 AND pg_catalog.pg_class.relname = $2 AND pg_catalog.pg_table_is_visible(pg_catalog.pg_class.oid) AND pg_catalog.pg_namespace.nspname != $3
2024-07-31 15:27:14,434 INFO sqlalchemy.engine.Engine [generated in 0.00016s] ('S', 'fakemodel_id_sequence', 'pg_catalog')
2024-07-31 15:27:14,438 INFO sqlalchemy.engine.Engine 
            SELECT oid, table_name
            FROM (
                SELECT table_oid AS oid, table_name,              database_name, schema_name FROM duckdb_tables()
                UNION ALL BY NAME
                SELECT view_oid AS oid , view_name AS table_name, database_name, schema_name FROM duckdb_views()
            )
            WHERE schema_name NOT LIKE 'pg\_%' ESCAPE '\'
            AND table_name = $1

2024-07-31 15:27:14,438 INFO sqlalchemy.engine.Engine [cached since 0.03565s ago] ('fake',)
2024-07-31 15:27:14,443 INFO sqlalchemy.engine.Engine SELECT pg_catalog.pg_class.relname 
FROM pg_catalog.pg_class JOIN pg_catalog.pg_namespace ON pg_catalog.pg_namespace.oid = pg_catalog.pg_class.relnamespace 
WHERE pg_catalog.pg_class.relkind = $1 AND pg_catalog.pg_class.relname = $2 AND pg_catalog.pg_table_is_visible(pg_catalog.pg_class.oid) AND pg_catalog.pg_namespace.nspname != $3
2024-07-31 15:27:14,443 INFO sqlalchemy.engine.Engine [cached since 0.009541s ago] ('S', 'fakemodel_id_sequence', 'pg_catalog')
2024-07-31 15:27:14,447 INFO sqlalchemy.engine.Engine CREATE SEQUENCE fakemodel_id_sequence
2024-07-31 15:27:14,447 INFO sqlalchemy.engine.Engine [no key 0.00007s] ()
2024-07-31 15:27:14,447 INFO sqlalchemy.engine.Engine 
CREATE TABLE fake (
        id INTEGER NOT NULL, 
        name VARCHAR, 
        PRIMARY KEY (id)
)


2024-07-31 15:27:14,447 INFO sqlalchemy.engine.Engine [no key 0.00006s] ()
2024-07-31 15:27:14,449 INFO sqlalchemy.engine.Engine INSERT INTO fake (id, name) SELECT p0::INTEGER, p1::VARCHAR FROM (VALUES (nextval('fakemodel_id_sequence'), $1, 0), (nextval('fakemodel_id_sequence'), $2, 1)) AS imp_sen(p0, p1, sen_counter) ORDER BY sen_counter RETURNING fake.id, fake.id AS id__1
2024-07-31 15:27:14,450 INFO sqlalchemy.engine.Engine [generated in 0.00006s (insertmanyvalues) 1/1 (ordered)] ('Frank', 'Joe')
2024-07-31 15:27:14,452 INFO sqlalchemy.engine.Engine SELECT fake.id, fake.name 
FROM fake
2024-07-31 15:27:14,452 INFO sqlalchemy.engine.Engine [generated in 0.00009s] ()
FakeModel(id=1, name=Frank)
FakeModel(id=2, name=Joe)
2024-07-31 15:27:14,452 INFO sqlalchemy.engine.Engine SELECT fake.id, fake.name 
FROM fake
2024-07-31 15:27:14,453 INFO sqlalchemy.engine.Engine [cached since 0.0008197s ago] ()
FakeModel(id=1, name=Frank)
FakeModel(id=2, name=Joe)
2024-07-31 15:27:14,453 INFO sqlalchemy.engine.Engine SELECT fake.id, fake.name 
FROM fake
2024-07-31 15:27:14,453 INFO sqlalchemy.engine.Engine [cached since 0.001428s ago] ()
FakeModel(id=1, name=Frank)
FakeModel(id=2, name=Joe)
2024-07-31 15:27:14,454 INFO sqlalchemy.engine.Engine COMMIT

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