مسابقه حضوری ۳ آذر حذف شده و این مسابقه تعیین‌کننده‌ی رتبه و جایزه‌ی شما است. اطلاعات بیشتر را می‌توانید در این‌جا کسب کنید.

لینک‌های مفید برای شرکت در مسابقه:

در طول مسابقه، می‌توانید سؤالات خود را از قسمت «سؤال بپرسید» مطرح کنید.

جدول یا آبجکت؟


دیتابیس

علی به‌تازگی با کتاب‌خانه‌ی GORM که یک ORM به زبان Go است آشنا شده. او داکیومنت این کتاب‌خانه را مطالعه کرده و به مرور زمان در حال پی بردن به امکانات جذاب این کتاب‌خانه است. از آن‌جایی که علی فرد کنجکاوی است، او تصمیم گرفته تا سورس کد این کتاب‌خانه را مطالعه کند و دریابد که این کتاب‌خانه چگونه کار می‌کند. از آن‌جایی که کد این کتاب‌خانه بسیار طولانی است و علی حوصله‌ی خواندن آن را ندارد. به دنبال نسخه‌ی ساده‌ای از یک ORM مشابه GORM می‌گردد تا بتواند با خواندن سورس کد آن، نحوه‌ی کارکرد آن را متوجه شود. از شما می‌خواهیم تا چنین کتاب‌خانه‌ای را برای علی پیاده‌سازی کنید.

جزئیات پروژه🔗

پروژه‌ی اولیه را از این لینک دانلود کنید. ساختار فایل‌های پروژه به‌صورت زیر است:

orm
├── test
│   └── orm_sample_test.go
├── configurators.go
├── driver.go
├── go.mod
├── go.sum
├── orm.go
└── query.go
Plain text

استراکت 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
SQL

کوئری‌های تولیدشده توسط QueryBuilder باید مطابق با مثال‌های زیر باشند (فاصله و کوچکی و بزرگی حروف مهم است):

مثال ۱:🔗

s := orm.NewQueryBuilder[Dummy]()
s.Table("users")
s.ToSql()
Go

کوئری ایجادشده:

SELECT * FROM users
SQL

مثال ۲:🔗

s.Table("users").SetDriver(orm.Drivers.SQLite3).
    Where("age", 10).
    AndWhere("age", "<", 10).
    Where("name", "CodeCup").
    OrWhere("age", ">", 11)
    .ToSql()
Go

کوئری ایجادشده در درایور SQLite3:

SELECT * FROM users WHERE age = ? AND age < ? AND name = ? OR age > ?
SQL

مثال ۳:🔗

orm.NewQueryBuilder[Dummy]().
    SetDriver(orm.Drivers.PostgreSQL).
    Table("users").
    WhereIn("id", 1, 2, 3, 4, 5, 6).
    ToSql()
Go

کوئری ایجادشده در درایور PostgreSQL:

SELECT * FROM users WHERE id IN ($1, $2, $3, $4, $5, $6)
SQL

نکات🔗

  • توجه داشته باشید که در این ORM شما باید مستقیماً با دیتابیس در ارتباط باشید و امکان هندل کردن داده‌ها به‌صورت in-memory را ندارید.
  • در صورت نیاز، می‌توانید فایل‌های جدیدی در برنامه تعریف کنید، اما امکان ایجاد دایرکتوری جدید را ندارید.
  • می‌توانید از کتاب‌خانه‌های 3rd party در کد خود استفاده کنید. در این‌صورت، باید فایل‌های go.mod و go.sum را نیز در فایل زیپ ارسالی خود قرار دهید.

آن‌چه باید آپلود کنید🔗

پس از پیاده‌سازی برنامه، محتویات دایرکتوری اصلی برنامه را زیپ کرده و آپلود کنید، به‌طوری که وقتی آن را باز می‌کنیم، با فایل driver.go و سایر فایل‌های برنامه مواجه شویم.

ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.