Skip to content

Commit 84ad0e2

Browse files
author
Valeriya Popova
committed
ydb sqlalchemy experiment
1 parent fe58bdf commit 84ad0e2

File tree

15 files changed

+1217
-1
lines changed

15 files changed

+1217
-1
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ ydb.egg-info/
44
/tox
55
/venv
66
/ydb_certs
7+
/ydb_data
78
/tmp
9+
.coverage
10+
/cov_html
11+
/build
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import datetime
2+
import logging
3+
import argparse
4+
import sqlalchemy as sa
5+
from sqlalchemy import orm, exc, sql
6+
from sqlalchemy import Table, Column, Integer, String, Float, TIMESTAMP
7+
from ydb._sqlalchemy import register_dialect
8+
9+
from fill_tables import fill_all_tables, to_days
10+
from models import Base, Series, Episodes
11+
12+
13+
def describe_table(engine, name):
14+
inspect = sa.inspect(engine)
15+
print(f"describe table {name}:")
16+
for col in inspect.get_columns(name):
17+
print(f"\t{col['name']}: {col['type']}")
18+
19+
20+
def simple_select(conn):
21+
stm = sa.select(Series).where(Series.series_id == 1)
22+
res = conn.execute(stm)
23+
print(res.one())
24+
25+
26+
def simple_insert(conn):
27+
stm = Episodes.__table__.insert().values(
28+
series_id=3, season_id=6, episode_id=1, title="TBD"
29+
)
30+
conn.execute(stm)
31+
32+
33+
def test_types(conn):
34+
types_tb = Table(
35+
"test_types",
36+
Base.metadata,
37+
Column("id", Integer, primary_key=True),
38+
Column("str", String),
39+
Column("num", Float),
40+
Column("dt", TIMESTAMP),
41+
)
42+
types_tb.drop(bind=conn.engine, checkfirst=True)
43+
types_tb.create(bind=conn.engine, checkfirst=True)
44+
45+
stm = types_tb.insert().values(
46+
id=1,
47+
str=b"Hello World!",
48+
num=3.1415,
49+
dt=datetime.datetime.now(),
50+
)
51+
conn.execute(stm)
52+
53+
# GROUP BY
54+
stm = sa.select(types_tb.c.str, sa.func.max(types_tb.c.num)).group_by(
55+
types_tb.c.str
56+
)
57+
rs = conn.execute(stm)
58+
for x in rs:
59+
print(x)
60+
61+
62+
def run_example_orm(engine):
63+
Base.metadata.bind = engine
64+
Base.metadata.drop_all()
65+
Base.metadata.create_all()
66+
67+
session = orm.sessionmaker(bind=engine)()
68+
69+
rs = session.query(Episodes).all()
70+
for e in rs:
71+
print(f"{e.episode_id}: {e.title}")
72+
73+
fill_all_tables(session.connection())
74+
75+
try:
76+
session.add_all(
77+
[
78+
Episodes(
79+
series_id=2,
80+
season_id=1,
81+
episode_id=1,
82+
title="Minimum Viable Product",
83+
air_date=to_days("2014-04-06"),
84+
),
85+
Episodes(
86+
series_id=2,
87+
season_id=1,
88+
episode_id=2,
89+
title="The Cap Table",
90+
air_date=to_days("2014-04-13"),
91+
),
92+
Episodes(
93+
series_id=2,
94+
season_id=1,
95+
episode_id=3,
96+
title="Articles of Incorporation",
97+
air_date=to_days("2014-04-20"),
98+
),
99+
Episodes(
100+
series_id=2,
101+
season_id=1,
102+
episode_id=4,
103+
title="Fiduciary Duties",
104+
air_date=to_days("2014-04-27"),
105+
),
106+
Episodes(
107+
series_id=2,
108+
season_id=1,
109+
episode_id=5,
110+
title="Signaling Risk",
111+
air_date=to_days("2014-05-04"),
112+
),
113+
]
114+
)
115+
session.commit()
116+
except exc.DatabaseError:
117+
print("Episodes already added!")
118+
session.rollback()
119+
120+
rs = session.query(Episodes).all()
121+
for e in rs:
122+
print(f"{e.episode_id}: {e.title}")
123+
124+
rs = session.query(Episodes).filter(Episodes.title == "abc??").all()
125+
for e in rs:
126+
print(e.title)
127+
128+
print("Episodes count:", session.query(Episodes).count())
129+
130+
max_episode = session.query(sql.expression.func.max(Episodes.episode_id)).scalar()
131+
print("Maximum episodes id:", max_episode)
132+
133+
session.add(
134+
Episodes(
135+
series_id=2,
136+
season_id=1,
137+
episode_id=max_episode + 1,
138+
title="Signaling Risk",
139+
air_date=to_days("2014-05-04"),
140+
)
141+
)
142+
143+
print("Episodes count:", session.query(Episodes).count())
144+
145+
146+
def run_example_core(engine):
147+
with engine.connect() as conn:
148+
# raw sql
149+
rs = conn.execute("SELECT 1 AS value")
150+
print(rs.fetchone()["value"])
151+
152+
fill_all_tables(conn)
153+
154+
for t in "series seasons episodes".split():
155+
describe_table(engine, t)
156+
157+
tb = sa.Table("episodes", sa.MetaData(engine), autoload=True)
158+
stm = (
159+
sa.select([tb.c.title])
160+
.where(sa.and_(tb.c.series_id == 1, tb.c.season_id == 3))
161+
.where(tb.c.title.like("%"))
162+
.order_by(sa.asc(tb.c.title))
163+
# TODO: limit isn't working now
164+
# .limit(3)
165+
)
166+
rs = conn.execute(stm)
167+
print(rs.fetchall())
168+
169+
simple_select(conn)
170+
171+
simple_insert(conn)
172+
173+
# simple join
174+
stm = sa.select(
175+
[Episodes.__table__.join(Series, Episodes.series_id == Series.series_id)]
176+
).where(sa.and_(Series.series_id == 1, Episodes.season_id == 1))
177+
rs = conn.execute(stm)
178+
for row in rs:
179+
print(f"{row.series_title}({row.episode_id}): {row.title}")
180+
181+
rs = conn.execute(sa.select(Episodes).where(Episodes.series_id == 3))
182+
print(rs.fetchall())
183+
184+
# count
185+
cnt = conn.execute(sa.func.count(Episodes.episode_id)).scalar()
186+
print("Episodes cnt:", cnt)
187+
188+
# simple delete
189+
conn.execute(sa.delete(Episodes).where(Episodes.title == "TBD"))
190+
cnt = conn.execute(sa.func.count(Episodes.episode_id)).scalar()
191+
print("Episodes cnt:", cnt)
192+
193+
test_types(conn)
194+
195+
196+
def main():
197+
parser = argparse.ArgumentParser(
198+
formatter_class=argparse.RawDescriptionHelpFormatter,
199+
description="""\033[92mYandex.Database examples sqlalchemy usage.\x1b[0m\n""",
200+
)
201+
parser.add_argument(
202+
"-d",
203+
"--database",
204+
help="Name of the database to use",
205+
default="/local",
206+
)
207+
parser.add_argument(
208+
"-e",
209+
"--endpoint",
210+
help="Endpoint url to use",
211+
default="grpc://localhost:2136",
212+
)
213+
214+
args = parser.parse_args()
215+
register_dialect()
216+
engine = sa.create_engine(
217+
"yql:///ydb/",
218+
connect_args={"database": args.database, "endpoint": args.endpoint},
219+
)
220+
221+
logging.basicConfig(level=logging.INFO)
222+
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
223+
224+
run_example_core(engine)
225+
# run_example_orm(engine)
226+
227+
228+
if __name__ == "__main__":
229+
main()
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import iso8601
2+
3+
import sqlalchemy as sa
4+
from models import Base, Series, Seasons, Episodes
5+
6+
7+
def to_days(date):
8+
timedelta = iso8601.parse_date(date) - iso8601.parse_date("1970-1-1")
9+
return timedelta.days
10+
11+
12+
def fill_series(conn):
13+
data = [
14+
(
15+
1,
16+
"IT Crowd",
17+
"The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by "
18+
"Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry.",
19+
to_days("2006-02-03"),
20+
),
21+
(
22+
2,
23+
"Silicon Valley",
24+
"Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and "
25+
"Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley.",
26+
to_days("2014-04-06"),
27+
),
28+
]
29+
conn.execute(sa.insert(Series).values(data))
30+
31+
32+
def fill_seasons(conn):
33+
data = [
34+
(1, 1, "Season 1", to_days("2006-02-03"), to_days("2006-03-03")),
35+
(1, 2, "Season 2", to_days("2007-08-24"), to_days("2007-09-28")),
36+
(1, 3, "Season 3", to_days("2008-11-21"), to_days("2008-12-26")),
37+
(1, 4, "Season 4", to_days("2010-06-25"), to_days("2010-07-30")),
38+
(2, 1, "Season 1", to_days("2014-04-06"), to_days("2014-06-01")),
39+
(2, 2, "Season 2", to_days("2015-04-12"), to_days("2015-06-14")),
40+
(2, 3, "Season 3", to_days("2016-04-24"), to_days("2016-06-26")),
41+
(2, 4, "Season 4", to_days("2017-04-23"), to_days("2017-06-25")),
42+
(2, 5, "Season 5", to_days("2018-03-25"), to_days("2018-05-13")),
43+
]
44+
conn.execute(sa.insert(Seasons).values(data))
45+
46+
47+
def fill_episodes(conn):
48+
data = [
49+
(1, 1, 1, "Yesterday's Jam", to_days("2006-02-03")),
50+
(1, 1, 2, "Calamity Jen", to_days("2006-02-03")),
51+
(1, 1, 3, "Fifty-Fifty", to_days("2006-02-10")),
52+
(1, 1, 4, "The Red Door", to_days("2006-02-17")),
53+
(1, 1, 5, "The Haunting of Bill Crouse", to_days("2006-02-24")),
54+
(1, 1, 6, "Aunt Irma Visits", to_days("2006-03-03")),
55+
(1, 2, 1, "The Work Outing", to_days("2006-08-24")),
56+
(1, 2, 2, "Return of the Golden Child", to_days("2007-08-31")),
57+
(1, 2, 3, "Moss and the German", to_days("2007-09-07")),
58+
(1, 2, 4, "The Dinner Party", to_days("2007-09-14")),
59+
(1, 2, 5, "Smoke and Mirrors", to_days("2007-09-21")),
60+
(1, 2, 6, "Men Without Women", to_days("2007-09-28")),
61+
(1, 3, 1, "From Hell", to_days("2008-11-21")),
62+
(1, 3, 2, "Are We Not Men?", to_days("2008-11-28")),
63+
(1, 3, 3, "Tramps Like Us", to_days("2008-12-05")),
64+
(1, 3, 4, "The Speech", to_days("2008-12-12")),
65+
(1, 3, 5, "Friendface", to_days("2008-12-19")),
66+
(1, 3, 6, "Calendar Geeks", to_days("2008-12-26")),
67+
(1, 4, 1, "Jen The Fredo", to_days("2010-06-25")),
68+
(1, 4, 2, "The Final Countdown", to_days("2010-07-02")),
69+
(1, 4, 3, "Something Happened", to_days("2010-07-09")),
70+
(1, 4, 4, "Italian For Beginners", to_days("2010-07-16")),
71+
(1, 4, 5, "Bad Boys", to_days("2010-07-23")),
72+
(1, 4, 6, "Reynholm vs Reynholm", to_days("2010-07-30")),
73+
]
74+
conn.execute(sa.insert(Episodes).values(data))
75+
76+
77+
def fill_all_tables(conn):
78+
Base.metadata.drop_all(conn.engine)
79+
Base.metadata.create_all(conn.engine)
80+
81+
fill_series(conn)
82+
fill_seasons(conn)
83+
fill_episodes(conn)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import sqlalchemy.orm as orm
2+
from sqlalchemy import Column, Integer, Unicode
3+
4+
5+
Base = orm.declarative_base()
6+
7+
8+
class Series(Base):
9+
__tablename__ = "series"
10+
11+
series_id = Column(Integer, primary_key=True)
12+
title = Column(Unicode)
13+
series_info = Column(Unicode)
14+
release_date = Column(Integer)
15+
16+
17+
class Seasons(Base):
18+
__tablename__ = "seasons"
19+
20+
series_id = Column(Integer, primary_key=True)
21+
season_id = Column(Integer, primary_key=True)
22+
title = Column(Unicode)
23+
first_aired = Column(Integer)
24+
last_aired = Column(Integer)
25+
26+
27+
class Episodes(Base):
28+
__tablename__ = "episodes"
29+
30+
series_id = Column(Integer, primary_key=True)
31+
season_id = Column(Integer, primary_key=True)
32+
episode_id = Column(Integer, primary_key=True)
33+
title = Column(Unicode)
34+
air_date = Column(Integer)

test-requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ sqlalchemy==1.4.26
4747
pylint-protobuf
4848
cython
4949
freezegun==1.2.2
50+
grpcio-tools
51+
pytest-cov

tests/sqlalchemy/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pytest
2+
import sqlalchemy as sa
3+
4+
from ydb._sqlalchemy import register_dialect
5+
6+
7+
@pytest.fixture(scope="module")
8+
def engine(endpoint, database):
9+
register_dialect()
10+
engine = sa.create_engine(
11+
"yql:///ydb/",
12+
connect_args={"database": database, "endpoint": endpoint},
13+
)
14+
15+
yield engine
16+
engine.dispose()
17+
18+
19+
@pytest.fixture(scope="module")
20+
def connection(engine):
21+
with engine.connect() as conn:
22+
yield conn

0 commit comments

Comments
 (0)