علی بهتازگی با کتابخانهی GORM که یک ORM به زبان Go است آشنا شده. او داکیومنت این کتابخانه را مطالعه کرده و به مرور زمان در حال پی بردن به امکانات جذاب این کتابخانه است. از آنجایی که علی فرد کنجکاوی است، او تصمیم گرفته تا سورس کد این کتابخانه را مطالعه کند و دریابد که این کتابخانه چگونه کار میکند. از آنجایی که کد این کتابخانه بسیار طولانی است و علی حوصلهی خواندن آن را ندارد. به دنبال نسخهی سادهای از یک ORM مشابه GORM میگردد تا بتواند با خواندن سورس کد آن، نحوهی کارکرد آن را متوجه شود. از شما میخواهیم تا چنین کتابخانهای را برای علی پیادهسازی کنید.
جزئیات پروژه
پروژهی اولیه را از این لینک دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
orm
├── test
│ └── orm_sample_test.go
├── configurators.go
├── driver.go
├── go.mod
├── go.sum
├── orm.go
└── query.go
استراکت EntityConfigurator
این استراکت در فایل configurators.go
تعریف شده است. موجودیتهای برنامه میتوانند شامل اطلاعات مختفی نظیر نام جدول باشند. این اطلاعات میتوانند در این استراکت ذخیره شوند. متدی با نام Table
برای این نوع داده تعریف شده است که فیلد table
(نام جدول) مربوط به موجودیت را مقداردهی میکند. در صورت نیاز، میتوانید ساختار این استراکت را تغییر دهید، اما نباید امضای متد Table
را تغییر دهید.
استراکت Driver
قرار است ORM ما بتواند به دیتابیسهای مختلف متصل شود. درایورها، جزئیات پیادهسازی مربوط به DBMS های مختلف را مشخص میکنند. استراکت Driver
در فایل driver.go
تعریف شده است. همچنین یک استراکت بدون نام در متغیر Drivers
تعریف شده که شامل دو درایور در قالب فیلدهای PostgreSQL
و SQLite3
است. نام درایور PostgreSQL
را برابر با postgres
و نام درایور SQLite3
را برابر با sqlite3
قرار دهید. یک فیلد با نام PlaceHolderGenerator
نیز وجود دارد که مقدار آن یک تابع است که با دریافت تعداد پارامترهای موجود در یک کوئری SQL ، آرایهای از رشتهها که بیانگر placeholder مقادیر موجود در کوئری هستند را برمیگرداند. این تابع را برای دو درایور تعریفشده بهصورت زیر پیادهسازی کنید:
- در درایور
PostgreSQL
، تابعPlaceHolderGenerator
باید با دریافتn
، مجموعهای از رشتهها به فرم$i
(بهطوری کهi
از1
تاn
است) را برگرداند. برای مثال، اگر مقدارn
برابر با3
باشد، خروجی باید بهترتیب شامل رشتههای$1
،$2
و$3
باشد. - در درایور
SQLite3
، تابعPlaceHolderGenerator
باید با دریافتn
، یک آرایه از رشتهها بهطولn
که مقادیر آن همگی برابر با?
هستند برگرداند.
اینترفیس Entity
این اینترفیس در فایل orm.go
تعریف شده و شامل یک متد ConfigureEntity(e *EntityConfigurator)
است. موجودیتهای برنامه باید این اینترفیس را پیادهسازی کنند. متد ConfigureEntity
این اینترفیس، پیکربندی اولیهی یک Entity
را انجام میدهد (مثلاً نگهداری نام جدول مربوط به موجودیت) که به ازای هر Entity
توسط برنامهنویس آن پیادهسازی میشود.
استراکت ConnectionConfig
این استراکت شامل پوینتری به یک sql.DB
، پوینتری به یک درایور و آرایهای از موجودیتهای برنامه است. از این ConnectionConfig
ها در تابع SetupConnections
استفاده میشود.
تابع SetupConnections
این تابع با دریافت تعداد نامشخصی ConnectionConfig
، اتصال مربوط به کانکشنها را برقرار میکند و مقداردهیهای اولیهی لازم برای کانکشنها را انجام میدهد.
تابع Find
این تابع با دریافت کلید اصلی (عددی) یک موجودیت از نوع T
(جنریک)، اطلاعات مربوط به سطر موردنظر از دیتابیس را باید در قالب یک آبجکت از نوع T
برگرداند. نام ستونها در جدول بهصورت snake_case
هستند، اما نام فیلدهای نوع دادهی T
بهصورت PascalCase
است. برای مثال، اگر در یک موجودیت، فیلدی با نام BodyText
داشته باشیم، در جدول متناظر با موجودیت، ستونی با نام body_text
وجود خواهد داشت. تضمین میشود که فیلدها برای ستونهای مختلف جدول تعریف شدهاند.
تابع All
این تابع، اطلاعات مربوط به سطرهای جدول موردنظر از دیتابیس (با توجه به نوع دادهی جنریک) را باید در قالب آرایهای از T
ها برگرداند.
تابع First
این تابع باید اطلاعات اولین سطر (بر اساس کلید اصلی جدول) در جدول موردنظر (با توجه به نوع دادهی جنریک) را در قالب آبجکتی از نوع T
برگرداند.
تابع Last
این تابع باید اطلاعات آخرین سطر (بر اساس کلید اصلی جدول) در جدول موردنظر (با توجه به نوع دادهی جنریک) را در قالب آبجکتی از نوع T
برگرداند.
استراکت QueryBuilder
این استراکت شامل اطلاعات یک کوئری برای تبدیل به فرمت SQL است که در فایل query.go
تعریف شده. آن را به دلخواه پیادهسازی کنید.
تابع NewQueryBuilder
را بهگونهای پیادهسازی کنید که یک نمونهی جدید از نوع QueryBuilder
برگرداند.
متدهای زیر برای استراکت QueryBuilder
تعریف شده که باید آنها را پیادهسازی کنید:
- متد
Get
: این متد باید کوئری مدنظر (با توجه به متدهای فراخوانیشده رویQueryBuilder
) را اجرا کرده و نتیجه را در قالب یک آبجکت از نوعOUTPUT
(بهصورت جنریک) برگرداند. در اینصورت، تنها اولین نتیجه برمیگردد. - متد
All
: این متد باید کوئری مدنظر (با توجه به متدهای فراخوانیشده رویQueryBuilder
) را اجرا کرده و همهی نتایج را در قالب آرایهای ازOUTPUT
ها (بهصورت جنریک) برگرداند. - متد
OrderBy
: این متد با دریافت نام یک ستون و نحوهی ترتیب (ASC
یاDESC
، بهمعنای صعودی یا نزولی) باید یک بخشORDER BY
به کوئری اضافه کند. - متد
Where
: این متد میتواند حداقل دو آرگومان دریافت کند:- اگر دو آرگومان به این متد پاس داده شود، اولین آرگومان نام یک ستون و دومین آرگومان مقدار مدنظر خواهد بود. شرط برابر بودن مقدار ستون موردنظر با مقدار واردشده باید به کوئری اضافه شود. در صورتی که شرطی از قبل به کوئری اضافه شده باشد، شرط فعلی باید با شرطهای قبلی
AND
شود. - اگر سه آرگومان به این متد پاس داده شود، اولین آرگومان نام یک ستون، دومین آرگومان یک عملگر (نظیر
=
) و سومین آرگومان مقدار مدنظر خواهد بود. شرط واردشده باید به کوئری اضافه شود. در صورتی که شرطی از قبل به کوئری اضافه شده باشد، شرط فعلی باید با شرطهای قبلیAND
شود. - اگر بیش از سه آرگومان به این متد پاس داده شود و آرگومان دوم برابر با رشتهی
IN
باشد، آرگومان اول برابر با نام ستون موردنظر خواهد بود. آرگومان سوم به بعد، مقادیری هستند که مقدار ستون موردنظر باید حداقل برابر با یکی از آنها باشد.
- اگر دو آرگومان به این متد پاس داده شود، اولین آرگومان نام یک ستون و دومین آرگومان مقدار مدنظر خواهد بود. شرط برابر بودن مقدار ستون موردنظر با مقدار واردشده باید به کوئری اضافه شود. در صورتی که شرطی از قبل به کوئری اضافه شده باشد، شرط فعلی باید با شرطهای قبلی
- متد
WhereIn
: عملکرد این متد، مشابه حالت سوم متدWhere
است. - متد
AndWhere
: عملکرد این متد، مشابه متدWhere
است. - متد
OrWhere
: عملکرد این متد، مشابه متدWhere
است، با این تفاوت که اگر شرطی از قبل در کوئری موجود باشد، شرط جدید با شرطهای قبلیOR
خواهد شد. - متد
Limit
: این متد با دریافت یک عدد، limit موجود در کوئری را مشخص میکند. - متد
Offset
: این متد با دریافت یک عدد، offset موجود در کوئری را مشخص میکند. - متد
Table
: این متد با دریافت نام جدول در قالب یک رشته، نام جدولی که کوئری باید روی آن اجرا شود را مشخص میکند. - متد
GroupBy
: این متد با دریافت تعداد نامشخصی رشته، یک عبارت group by به کوئری اضافه میکند. در صورتی که این متد چند بار فراخوانی شود، ستونها باید بهترتیب به بخش group by کوئری اضافه شوند. - متد
Select
: این متد با دریافت تعداد نامشخصی رشته، مشخص میکند که مقدار کدام ستونها از جدول دیتابیس دریافت شوند. اگر این متد فراخوانی نشود، همهی ستونها (*
) باید از جدول موجود در دیتابیس دریافت شوند. اگر این متد چند بار فراخوانی شود، ستونها باید بهترتیب به کوئری اضافه شوند. - متد
SetDriver
: این متد با دریافت یکDriver
، باید موارد موردنیاز برای کوئری بیلدر (نظیرPlaceHolderGenerator
) را به موارد موجود درDriver
تغییر دهد. - متد
ToSql
: این متد باید کوئری SQL تولیدشده را در قالب یک رشته و مقادیر پارامترهای موجود در کوئری را در قالب آرایهای ازinterface{}
ها برگرداند. اگر اولین فراخوانی روی یک آبجکت جدیدQueryBuilder
مربوط به متدToSql
بود، مقدار خروجیerror
را برابر با یک رشتهی غیر از خالی قرار دهید. در غیر اینصورت،error
راnil
برگردانید.
نکته: در صورتی که چند شرط AND
و OR
شوند، صرفاً کافی است تا آنها را با استفاده از عملگرهای مربوطه (بین پرانتز) کنار یکدیگر قرار دهید. مثال:
WHERE cond1 OR cond2 AND cond3 OR cond4
کوئریهای تولیدشده توسط QueryBuilder
باید مطابق با مثالهای زیر باشند (فاصله و کوچکی و بزرگی حروف مهم است):
مثال ۱:
s := orm.NewQueryBuilder[Dummy]()
s.Table("users")
s.ToSql()
کوئری ایجادشده:
SELECT * FROM users
مثال ۲:
s.Table("users").SetDriver(orm.Drivers.SQLite3).
Where("age", 10).
AndWhere("age", "<", 10).
Where("name", "CodeCup").
OrWhere("age", ">", 11)
.ToSql()
کوئری ایجادشده در درایور SQLite3:
SELECT * FROM users WHERE age = ? AND age < ? AND name = ? OR age > ?
مثال ۳:
orm.NewQueryBuilder[Dummy]().
SetDriver(orm.Drivers.PostgreSQL).
Table("users").
WhereIn("id", 1, 2, 3, 4, 5, 6).
ToSql()
کوئری ایجادشده در درایور PostgreSQL:
SELECT * FROM users WHERE id IN ($1, $2, $3, $4, $5, $6)
نکات
- توجه داشته باشید که در این ORM شما باید مستقیماً با دیتابیس در ارتباط باشید و امکان هندل کردن دادهها بهصورت in-memory را ندارید.
- در صورت نیاز، میتوانید فایلهای جدیدی در برنامه تعریف کنید، اما امکان ایجاد دایرکتوری جدید را ندارید.
- میتوانید از کتابخانههای 3rd party در کد خود استفاده کنید. در اینصورت، باید فایلهای
go.mod
وgo.sum
را نیز در فایل زیپ ارسالی خود قرار دهید.
آنچه باید آپلود کنید
پس از پیادهسازی برنامه، محتویات دایرکتوری اصلی برنامه را زیپ کرده و آپلود کنید، بهطوری که وقتی آن را باز میکنیم، با فایل driver.go
و سایر فایلهای برنامه مواجه شویم.
ارسال پاسخ برای این سؤال