
علی بهتازگی با کتابخانهی 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 و سایر فایلهای برنامه مواجه شویم.
ارسال پاسخ برای این سؤال