Ачааллж байна...
Өмнөх хичээлд бид Python-оос PostgreSQL-тэй холбогдож, query ажиллуулсан. Гэвч тэр query-нүүд бүгд тогтмол байсан: "бүх номыг тоол", "хамгийн эртний гурван ном." Бодит програмд query-ний ихэнх нь гаднаас ирсэн утгатай ажилладаг: хэрэглэгч хайлтын талбарт бичсэн нэр, вэб хаягт байгаа номын дугаар, маягтаар илгээсэн имэйл. FastAPI сургалтын endpoint бүр ийм утгыг хүлээн авдаг байсан — /books/{book_id} гэх мэт.
Энэ хичээлийн асуулт энгийн: гаднаас ирсэн утгыг SQL query-д хэрхэн оруулах вэ? Python мэддэг хүн бүрийн толгойд нэг хариулт шууд орж ирдэг — f-string-ээр текстэд залгах. Энэ нь ажилладаг. Энэ хичээлд бид эхлээд яг ингэж хийж, дараа нь энэ арга хоёр өөр замаар хэрхэн эвдэрдгийг харна: нэг нь гэмгүй — апостроф бүхий нэр; нөгөө нь аюултай — SQL injection гэдэг, вэб програмын түүхэн дэх хамгийн их хохирол учруулсан алдааны нэг.
SQL injection-ийг бид өөрийн компьютер дээрх, өөрийн үүсгэсэн туршилтын өгөгдлийн сан дээр харуулна. Зорилго нь халдлага сурах биш, харин параметрүүд яагаад байдгийг, яагаад тэдгээрийг хэзээ ч алгасаж болохгүйг мартагдашгүйгээр ойлгох юм. Мөн нэг сайхан мэдээ бий: FastAPI сургалтад SQLModel-ийн select().where()-ээр бичсэн таны бүх query энэ аюулаас аль хэдийн хамгаалагдсан байсан. Энэ хичээлийн төгсгөлд яагаад гэдгийг echo гаралтаас өөрийн нүдээр харна.
Архивын албанд хоёр төрлийн хүсэлт хүлээн авах журам бий гэж бодъё. Эхний журамд зочин ажилтанд бүтэн тушаалын текст өгдөг бөгөөд ажилтан түүнийг үг бүрээр нь биелүүлдэг. Ажилтан “___ нэртэй гишүүний хавтсыг гарга” гэсэн загварыг хананд наасан, зочин хоосон зайд нэрээ бичээд текстийг бүтнээр нь өгнө. Шударга зочин "Бат" гэж бичнэ. Харин ухаалаг хорон санаатай зочин "Бат, мөн бүх гишүүний хавтсыг гарга" гэж бичнэ — тэгээд ажилтан бүтэн текстийг тушаал гэж уншиж, бүх хавтсыг гаргана. Зочин зөвхөн нэр бичих ёстой байсан хоосон зайд тушаал бичжээ.
Хоёр дахь журамд тушаал болон утга тусдаа ирдэг. Ажилтан урьдчилан хэвлэсэн маягттай: "Нэр: [ ]-тэй гишүүний хавтсыг гарга." Зочин зөвхөн дөрвөлжин нүдийг бөглөнө. Ажилтан нүдэнд юу ч бичигдсэн — "Бат, мөн бүх гишүүний хавтсыг гарга" гэж ч — түүнийг тушаал биш, хайх нэр гэж ойлгоно. Ийм урт нэртэй гишүүн байхгүй тул хоосон хариу өгнө. Хорон санаат текст хоргүй болжээ, учир нь тэр тушаалын хэсэг болох боломжгүй. SQL-ийн параметр бол яг энэ хоёр дахь журам.
pg-practice хавтсанд search_member.py файл үүсгэнэ. PowerShell-д виртуал орчин ба DATABASE_URL тохируулагдсан эсэхийг шалгаарай (өмнөх хичээлийн Алхам 1, 3).
python
import os
from sqlalchemy import create_engine, text
engine = create_engine(os.environ["DATABASE_URL"])
name = input("Гишүүний нэр: ")
sql = f"SELECT id, name, email FROM members WHERE name = '{name}'"
print("Илгээх SQL:", sql)
with engine.connect() as connection:
for row in connection.execute(text(sql)):
print(row.id, row.name, row.email)Код юу хийж байгааг уншъя. input() хэрэглэгчээс нэр асууна — энэ бол вэб маягтын хайлтын талбарын энгийн загвар. Дараагийн мөр f-string-ээр SQL текст бүтээж, хэрэглэгчийн бичсэн нэрийг дан хашилтын дотор залгана. print мөр сервер рүү яг юу явахыг харуулна — энэ нь хичээлийн гол хэрэгсэл. Сүүлийн хэсэг өмнөх хичээлийн танил хэв маяг.
Ажиллуулж, Бат гэж бичнэ:
Гишүүний нэр: Бат
Илгээх SQL: SELECT id, name, email FROM members WHERE name = 'Бат'
1 Бат bat@example.comИлгээсэн query юу гэж хэлж байна вэ гэвэл: “members хүснэгтээс нэр нь 'Бат' байгаа гишүүний id, нэр, имэйлийг харуул.” Бүлэг 2-ын WHERE хичээлийн яг ижил query. Бүх зүйл зөв ажиллаж байна. Олон хүн энэ мөчид зогсож, кодоо бэлэн гэж үздэг. Үргэлжлүүлье.
Жишээ 1: гэмгүй эвдрэл — апостроф
Номын сангийн шинэ гишүүн нь гадаад оюутан, нэр нь O’Brien. Скриптийг дахин ажиллуулж O'Brien гэж бичнэ:
Гишүүний нэр: O'Brien
Илгээх SQL: SELECT id, name, email FROM members WHERE name = 'O'Brien'Traceback-ийн төгсгөлд:
sqlalchemy.exc.ProgrammingError: (psycopg.errors.SyntaxError) syntax error at or near "Brien"
LINE 1: SELECT id, name, email FROM members WHERE name = 'O'Brien'
^Энэ мессеж юу гэж хэлж байна вэ гэвэл: “Brien гэдэг үгэн дээр эсвэл түүний ойролцоо синтаксийн алдаа гарлаа.” Давхаргуудыг өмнөх хичээлийн аргаар задалбал: SQLAlchemy-ийн түвшинд програмчлалын алдаа, psycopg-ийн түвшинд синтаксийн алдаа, server-ийн үг — Бүлэг 10-ын psql-ээс уншсантай ижил хэлбэр, ^ тэмдэгтэй.
Илгээсэн SQL-ийг хараарай. Бүлэг 3-ын INSERT хичээлд “Don’t Make Me Think”-ийн апострофтой яг ижил асуудалтай уулзсан: нэр доторх апостроф SQL-ийн текстийн төгсгөлийн тэмдэг гэж уншигдсан. Server 'O' гэсэн текстийг хараад, араас нь Brien' гэсэн утгагүй зүйл ирэхэд гацсан. Тэр үед бид апострофыг давхарлаж ('') гараар зассан. Гэвч хэрэглэгчээс ирж буй утгыг гараар засах боломжгүй — програм өөрөө хариуцах ёстой. Энэ эвдрэл гэмгүй: алдааны мессеж гарч, хэн нэгэн анзаарна. Дараагийн жишээ тийм биш.
Жишээ 2: аюултай эвдрэл — SQL injection
Хариуцлагын тухай нэг өгүүлбэр: энэ жишээг зөвхөн өөрийн компьютер дээрх өөрийн өгөгдлийн сан дээр туршина уу; бусдын систем дээр ийм оролт туршиж үзэх нь олон улсын хуулиар гэмт хэрэгт тооцогддог.
Скриптийг дахин ажиллуулж, нэрийн оронд яг дараах текстийг бичнэ:
' OR '1'='1Үр дүн:
Гишүүний нэр: ' OR '1'='1
Илгээх SQL: SELECT id, name, email FROM members WHERE name = '' OR '1'='1'
1 Бат bat@example.com
2 Сараа saraa@example.com
3 Тэмүүлэн None
4 Номин nomin@example.com
5 Ану anu@example.comАлдааны мессеж гараагүй. Хайлтын талбарт нэр биш, хачин тэмдэгтүүд бичсэн хэрэглэгч бүх гишүүний имэйл хаягийг авчээ. Илгээсэн SQL-ийг Бүлэг 2-ын дадлаар чангаар уншъя: “members хүснэгтээс нэр нь хоосон текст байгаа, эсвэл ‘1’ нь ‘1’-тэй тэнцүү байгаа мөрүүдийг харуул.” Хоёр дахь нөхцөл мөр бүрт үнэн — нэг нь нэгтэй үргэлж тэнцүү. Бүлэг 2-ын OR хичээлд сурсанчлан OR-ийн нэг тал үнэн бол мөр нэвтэрнэ. Бүх таван мөр нэвтэрсэн.
Хэрхэн ийм болсныг мөшгье. Хэрэглэгчийн текстийн эхний ' нь бидний query-ний нээсэн дан хашилтыг хаасан — нэр нь хоосон текст боллоо. Дараа нь OR '1'='1 нь текст биш, SQL-ийн шинэ нөхцөл болж query-д нэмэгдсэн. Төгсгөлд бидний query-ний хаах хашилт хэрэглэгчийн '1 текстийг хааж, бүх зүйл синтаксийн хувьд төгс зөв SQL болсон. Хэрэглэгч бидний query-ний бүтцийг өөрчилсөн — зүйрлэлийн "хоосон зайд тушаал бичсэн зочин." Энэ бол SQL injection: өгөгдөл байх ёстой газарт SQL код шургуулах.
Манай жижиг жишээнд таван имэйл задарсан. Бодит түүхэнд энэ төрлийн халдлагаар сая сая хэрэглэгчийн нууц үг, картын мэдээлэл хулгайлагдаж байсан. Илүү аюултай хувилбарууд бас бий — зарим драйвер, өгөгдлийн санд халдагч query-ний араас цэг таслал тавьж бүрэн шинэ тушаал (DELETE, DROP TABLE) залгаж чаддаг. Бид тэдгээрийг энд ажиллуулахгүй — механизм нь ижил тул нэг жишээ хангалттай. Гол сургамж: хэрэглэгчээс ирсэн утгыг SQL текстэд хэзээ ч залгаж болохгүй — f-string, +, .format(), % — аль ч аргаар.
Жишээ 3: засвар — параметр
Одоо хоёр дахь журам буюу маягтын нүд. search_member.py-ийг засна:
python
import os
from sqlalchemy import create_engine, text
engine = create_engine(os.environ["DATABASE_URL"], echo=True)
name = input("Гишүүний нэр: ")
sql = text("SELECT id, name, email FROM members WHERE name = :name")
with engine.connect() as connection:
for row in connection.execute(sql, {"name": name}):
print(row.id, row.name, row.email)Өөрчлөлт хоёр. SQL текстэд хэрэглэгчийн утгын оронд :name гэсэн орлуулагч бичигдсэн — хашилтгүйгээр. Энэ бол маягтын хоосон нүд: хоёр цэг ба нэр нь SQLAlchemy-д "энд утга орно" гэж хэлдэг. execute()-ийн хоёр дахь аргумент нь dict бөгөөд түүний түлхүүр орлуулагчийн нэртэй таарна: {"name": name}. Мөн echo-г асаасан — сервер рүү юу явж байгааг харах ёстой.
Хорон санаат текстийг дахин оролдъё — ' OR '1'='1:
INFO sqlalchemy.engine.Engine SELECT id, name, email FROM members WHERE name = %(name)s
INFO sqlalchemy.engine.Engine [generated in 0.00015s] {'name': "' OR '1'='1"}
INFO sqlalchemy.engine.Engine ROLLBACKНэг ч мөр хэвлэгдсэнгүй. Echo гаралт бүх нууцыг харуулж байна. Эхний мөр SQL текст — хэрэглэгчийн утга огт байхгүй, оронд нь %(name)s гэсэн орлуулагч (psycopg-ийн орлуулагчийн хэлбэр; SQLAlchemy бидний :name-ийг түүн рүү хөрвүүлсэн). Хоёр дахь мөр утгууд — тусдаа, dict хэлбэрээр. SQL текст ба утга сервер рүү хоёр тусдаа хэсэг болж явсан. Сервер эхлээд SQL текстийг уншиж бүтцийг нь ойлгосон: "нэр нь X-тэй тэнцүү мөрүүд." Дараа нь X-ийн утгыг ирсэн байдлаар нь, зөвхөн өгөгдөл гэж оруулсан. ' OR '1'='1 гэсэн 11 тэмдэгттэй нэртэй гишүүн байхгүй тул үр дүн хоосон. Query-ний бүтэц өөрчлөгдөх боломж огт байгаагүй.
Илгээсэн query юу гэж хэлж байна вэ гэвэл: “members хүснэгтээс нэр нь өгсөн утгатай яг тэнцүү гишүүдийн id, нэр, имэйлийг харуул” — утга нь юу ч байсан. Бат гэж бичвэл Бат ганцаараа гарна. O'Brien гэж бичвэл алдаа гарахгүй, хоосон үр дүн гарна — ийм гишүүн байхгүй учраас. Апострофыг давхарлах шаардлагагүй болсон: апостроф ч өгөгдлийн нэг тэмдэгт л. Нэг засвар хоёр асуудлыг хоёуланг нь шийдлээ.
Бүлэг 1-ийн анхны хичээлийг санаарай. Тэнд SQLModel-ийн нэг мөр WHERE book.year > ? гэсэн SQL болдог гэж харсан бөгөөд ? тэмдэгтийн тухай "энэ нь 'энд нэг утга орно' гэсэн орлуулагч бөгөөд 2000 гэдэг тоо query-ний текстэд шууд бичигддэггүй, харин тусад нь илгээгддэг; яагаад ингэдгийг Бүлэг 11-д SQL injection-ий тухай ярихдаа тайлбарлана" гэж амласан. Тэр амлалт одоо биелэв: SQLite ?, psycopg %(нэр)s гэж бичдэг ч санаа нь ижил — бүтэц ба өгөгдөл тусдаа.
Жишээ 4: SQLModel таныг аль хэдийн хамгаалж байсан
FastAPI сургалтад та select(Member).where(Member.name == name) гэх мэт бичдэг байсан. Тэр query сервер рүү юу явуулж байсныг харъя. pg-practice-д sqlmodel_check.py:
python
import os
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Member(SQLModel, table=True):
__tablename__ = "members"
id: int | None = Field(default=None, primary_key=True)
name: str
email: str | None = None
engine = create_engine(os.environ["DATABASE_URL"], echo=True)
with Session(engine) as session:
name = "' OR '1'='1"
members = session.exec(select(Member).where(Member.name == name)).all()
print(members)Энд бид SQLModel-д одоо байгаа members хүснэгтийн гурван баганыг тайлбарласан (__tablename__ нь хүснэгтийн нэрийг заана; бусад баганыг тайлбарлаагүй — унших query-д хангалттай). Echo гаралтын гол хоёр мөр:
INFO sqlalchemy.engine.Engine SELECT members.id, members.name, members.email
FROM members
WHERE members.name = %(name_1)s::VARCHAR
INFO sqlalchemy.engine.Engine [generated in 0.00021s] {'name_1': "' OR '1'='1"}Үр дүн: [] — хоосон жагсаалт. SQLModel where()-д өгсөн утга бүрийг автоматаар параметр болгосон: SQL текстэд орлуулагч (%(name_1)s), утга нь тусдаа. ::VARCHAR нь PostgreSQL-д "энэ утгыг текст гэж ойлго" гэсэн тайлбар — SQLAlchemy нэмсэн. FastAPI сургалтад таны бичсэн endpoint бүр ингэж хамгаалагдсан байсан, та мэдээгүй ч гэсэн. Аюул нь зөвхөн SQLModel-ийг алгасаж, text()-д f-string залгах үед л үүснэ — Бүлэг 14-т ORM ба цэвэр SQL-ийн хооронд сонгох тухай ярихдаа энэ мөчийг дахин санана.
Жишээ 5: олон параметр ба Python-ы огноо
Параметрийн хэдэн ч байж болох бөгөөд төрөл бүрийн утгыг дамжуулж чадна. Тодорхой хугацааны зээлүүдийг олъё — огноо нь Python-ы date объект:
python
import os
from datetime import date
from sqlalchemy import create_engine, text
engine = create_engine(os.environ["DATABASE_URL"])
sql = text("""
SELECT m.name, b.title, l.loaned_on
FROM loans AS l
INNER JOIN members AS m ON l.member_id = m.id
INNER JOIN books AS b ON l.book_id = b.id
WHERE l.loaned_on BETWEEN :start AND :end
ORDER BY l.loaned_on
""")
with engine.connect() as connection:
rows = connection.execute(sql, {"start": date(2026, 9, 20), "end": date(2026, 10, 2)})
for row in rows:
print(row.name, row.title, row.loaned_on)Үр дүн:
Бат Тунгалаг Тамир 2026-09-20
Номин Clean Code 2026-09-25
Сараа Clean Code 2026-10-01Энэ query юу гэж хэлж байна вэ гэвэл: "зээлүүдийг гишүүн ба номтой нийлүүлж, зээлсэн огноо нь өгсөн хоёр огнооны хооронд байгаа зээлүүдийн гишүүний нэр, номын нэр, огноог огноогоор эрэмбэлж харуул." Бүлэг 7-ийн гурван хүснэгтийн JOIN, Бүлэг 2-ын BETWEEN — бүгд танил. Шинэ нь: хоёр орлуулагч, dict-д хоёр түлхүүр, мөн утгууд нь текст биш, Python-ы date объект. psycopg тэдгээрийг PostgreSQL-ийн DATE төрөл рүү автоматаар хөрвүүлсэн — Бүлэг 10-т багануудыг DATE болгосон нь энд үр дүнгээ өгч байна. Буцаж ирсэн row.loaned_on ч мөн Python-ы date объект — текст биш. Python-ы """...""" олон мөрт текст нь урт query-г Бүлэг 8-ын дадлаар мөр мөрөөр нь бичих боломж олгосон.
Жишээ 6: параметртэй INSERT ба шинэ дугаар
Бүлэг 10-ын сүүлийн хичээлд "бодит програмд шинэ мөрийн id-г таамаглахгүй, харин INSERT-ээс буцааж авдаг — Бүлэг 11-д үүнийг харна" гэж хэлсэн. PostgreSQL-ийн RETURNING хэсэг INSERT-ийн дараа шинэ мөрийн утгуудыг буцаадаг. Мөн нэг шинэ зүйл: өмнөх хичээлд engine.connect()-ийн төгсгөлд ROLLBACK гарч байсныг санаарай — тэгвэл INSERT хадгалагдахгүй. Өгөгдөл өөрчлөхөд engine.begin() ашиглана: блок алдаагүй дуусвал автоматаар хадгалдаг (commit).
python
import os
from sqlalchemy import create_engine, text
engine = create_engine(os.environ["DATABASE_URL"])
new_member = {"name": "Болд", "email": "bold@example.com", "city": "Ховд"}
with engine.begin() as connection:
new_id = connection.execute(
text("""
INSERT INTO members (name, email, city)
VALUES (:name, :email, :city)
RETURNING id
"""),
new_member,
).scalar()
print("Шинэ гишүүний дугаар:", new_id)Үр дүн: Шинэ гишүүний дугаар: 7 (эсвэл өөр тоо — Бүлэг 10-т Болдыг нэмж, устгасан тул IDENTITY 6-г аль хэдийн хэрэглэсэн байж магадгүй; Бүлэг 4-т сурсанчлан PostgreSQL устгасан дугаарыг дахин олгодоггүй). Энэ query юу гэж хэлж байна вэ гэвэл: “members хүснэгтэд өгсөн нэр, имэйл, хоттой шинэ гишүүн нэмээд, түүнд олгосон id-г буцаа.” Гурван параметр — dict-ийн гурван түлхүүр. RETURNING id нь шинэ мөрийн дугаарыг SELECT-ийн үр дүн шиг буцааж, .scalar() түүнийг авсан. Энэ бол FastAPI сургалтад session.add() ба session.commit()-ийн дараа member.id гэнэт утгатай болдог байсны ард ажиллаж байсан механизм.
Туршилтын гишүүнийг параметртэй DELETE-ээр устгая — өгөгдлийг цэвэр байлгах:
python
with engine.begin() as connection:
connection.execute(text("DELETE FROM members WHERE id = :id"), {"id": new_id})engine.connect() ба engine.begin()-ийн ялгааг дараагийн хичээл ба Бүлэг 12-т бүрэн тайлбарлана. Одоохондоо дүрэм: уншихад connect(), өгөгдөл өөрчлөхөд begin().
Параметр нь утгыг орлох зориулалттай — хүснэгт, баганын нэрийг биш. Хэрэглэгч "аль баганаар эрэмбэлэх"-ийг сонгодог хайлтын хуудас бичье гэж бодъё. ORDER BY :column гэж бичээд {"column": "year"} дамжуулбал алдаа гарахгүй — гэвч эрэмбэлэлт ажиллахгүй. Сервер 'year' гэсэн тогтмол текстээр эрэмбэлнэ: мөр бүрт ижил утга, тэгэхээр дараалал өөрчлөгдөхгүй. Параметр үргэлж утга гэж ойлгогддог — яг энэ нь түүнийг аюулгүй болгодог чанар.
Хэрэв баганын нэрийг хэрэглэгч сонгох шаардлагатай бол зөвшөөрөгдсөн нэрсийн жагсаалт (whitelist) ашигла:
python
allowed = {"title", "year", "copies"}
column = "year" # хэрэглэгчээс ирсэн гэж үзье
if column not in allowed:
raise ValueError("Буруу багана")
sql = text(f"SELECT title, year FROM books ORDER BY {column}")Энд f-string ашиглаж байгаа ч энэ нь аюулгүй, учир нь column-ийн утга таны урьдчилан тодорхойлсон гурван нэрийн аль нэг байхаас өөр байх боломжгүй — хэрэглэгчийн текст шууд биш, таны шалгасан сонголт SQL-д орж байна. Дүрэм: утгад параметр, нэрэнд whitelist.
Хэрэв сонирхож байвал search_member.py-ийн параметртэй хувилбарыг өргөтгөж, хэрэглэгчийн бичсэн текстийг агуулсан нэрсийг олох LIKE хайлт болгоорой. Бүлэг 2-ын % тэмдэгтийг хаана тавих вэ гэдэг нь сонирхолтой асуулт: SQL текстэд '%' || :pattern || '%' гэж бичих үү, эсвэл Python-д {"pattern": f"%{text}%"} гэж утга бэлдэх үү? Хоёулаа аюулгүй — яагаад гэдгийг echo гаралтаас тайлбарлаж үзээрэй. Мөн Жишээ 2-ын f-string хувилбарт ' OR '1'='1-ийн оронд Бат' OR name = 'Сараа гэж бичвэл юу болохыг таамаглаад шалгаарай. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
Параметрийн утгыг dict-д өгөхөө мартах. connection.execute(text("... WHERE name = :name")) гэж хоёр дахь аргументгүй дуудвал:
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) A value is required for bind parameter 'name'Энэ мессеж юу гэж хэлж байна вэ гэвэл: “name гэсэн холбох параметрт утга шаардлагатай.” Орлуулагчийн нэр ба dict-ийн түлхүүр яг таарах ёстой — :name ба {"nme": ...} гэвэл ижил алдаа гарна.
Орлуулагчийг хашилтад хийх. WHERE name = ':name' гэж бичвэл алдаа гарахгүй — гэвч SQLAlchemy хашилт доторх :name-ийг орлуулагч биш, жинхэнэ текст гэж үзнэ. Query “:name” гэсэн нэртэй гишүүнийг хайж, үргэлж хоосон үр дүн өгнө. Орлуулагчийг хашилтгүй бич — хашилтыг драйвер өөрөө хариуцна.
Параметр ба f-string-ийг холих. text(f"SELECT ... WHERE name = '{name}' AND city = :city") — нэг утга параметр, нөгөө нь f-string. Энэ query injection-д эмзэг хэвээр: нэг ч залгасан хэрэглэгчийн утга бүх хамгаалалтыг хүчингүй болгоно. Хэрэглэгчээс ирсэн утга бүр параметр байх ёстой.
engine.connect()-ээр өгөгдөл өөрчлөөд хадгалагдсан гэж бодох. connect() блок дотор INSERT хийвэл алдаа гарахгүй, RETURNING шинэ id-г ч буцаана — гэвч блок дуусахад ROLLBACK болж, мөр алга болно. psql-ээр шалгахад гишүүн байхгүй байна. Өгөгдөл өөрчлөхдөө engine.begin() ашигла, эсвэл connect() дотор connection.commit() дууд.
Текстэн SQL-д хоёр цэгийг санамсаргүй ашиглах. text() нь :үг хэлбэрийг үргэлж орлуулагч гэж үздэг. Хэрэв SQL-д '10:30' гэх мэт цагийн текст эсвэл PostgreSQL-ийн ::date төрөл хөрвүүлэлт байвал SQLAlchemy тэдгээрийг параметр гэж андуурч болно. Ийм тохиолдолд PostgreSQL-ийн CAST(... AS DATE) бичлэгийг ашиглах эсвэл хоёр цэгийг \:-ээр тэмдэглэнэ.
Энэ хичээлд та гаднаас ирсэн утгыг SQL query-д аюулгүй дамжуулж сурлаа. Хэрэглэгчийн утгыг f-string-ээр SQL текстэд залгах нь хоёр замаар эвдэрдэг: апострофтой нэр синтаксийн алдаа өгдөг, харин ' OR '1'='1 шиг санаатай оролт query-ний бүтцийг өөрчилж, алдаагүйгээр бүх өгөгдлийг задруулдаг — SQL injection. Засвар нь параметр: SQL текстэд :нэр орлуулагч бичиж, утгуудыг dict-ээр тусад нь дамжуулна; сервер бүтцийг эхлээд ойлгож, утгыг зөвхөн өгөгдөл гэж оруулдаг тул ямар ч утга query-ний бүтцийг өөрчилж чадахгүй. SQLModel-ийн where() утга бүрийг автоматаар параметр болгодог тул FastAPI сургалтын таны код аль хэдийн хамгаалагдсан байсан. Параметр Python-ы date гэх мэт төрлүүдийг шууд дамжуулдаг; RETURNING шинэ мөрийн id-г буцаадаг; өгөгдөл өөрчлөхдөө engine.begin() ашиглана. Параметр утгыг л орлодог — баганын нэрэнд whitelist.
Дараагийн хичээлээр Бүлэг 11 дуусна. Бид энэ хоёр хичээлд ашигласан with engine.connect() ба with engine.begin()-ийн цаана юу болж байгааг тайлна: холболт гэж яг юу вэ, яагаад түүнийг заавал хаах ёстой, engine олон холболтыг хэрхэн дахин ашигладаг (pooling) вэ гэдгийг. Python-ы гүнзгий түвшний сургалтад context manager-ийн тухай сурсан бол тэр мэдлэг энд бүрэн хэрэглэгдэнэ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.