سلام به همه! من یک مهندس نرم افزار هستم که به برنامه نویسی سطح پایین، کامپایلرها و توسعه ابزار علاقه مند هستم.

در پایان سال 1402، اولین مقاله خود را منتشر کردم روی freeCodeCamp درباره روش ایجاد زبانی شبیه به SQL برای اجرای پرس و جوها روی مخازن محلی Git اگر زمینه بیشتری می خواهید، آن را بخوانید.

در آغاز سال 1403، پروژه با ویژگی‌های بیشتر و مشارکت‌کنندگان شگفت‌انگیز بزرگ‌تر و بزرگ‌تر شد، و من شروع به فکر کردن کردم: چه می‌شود اگر بتوانم نه‌تنها پرس‌و‌جوهایی شبیه به SQL را اجرا کنم. روی فایل های git اما روی هر نوع داده محلی و راه دور؟

در این مقاله شما را می خوانم روی سفری برای به روز رسانی طراحی پروژه GitQL برای استفاده به عنوان یک SDK. همچنین توضیح خواهم داد که چگونه از آن برای پیاده سازی پروژه FileQL استفاده کردم، که ابزاری برای اجرای پرس و جوی SQL مانند است. روی فایل های محلی

اولین مورد استفاده برای این ایده

اولین ایده من این بود که بتوانم از همان ویژگی های GitQL برای ساخت FileQL استفاده کنم، که ابزاری است که به شما امکان می دهد پرس و جو را اجرا کنید. روی یک سیستم فایل محلی

پس از آن، همه می توانند از پروژه GitQL به عنوان یک SDK برای ساخت XQL خود استفاده کنند. به عنوان مثال، LogQL، WeatherQL، CodeQL، AudioQL، BookQL، و غیره روی.

چگونه شروع کردم به فکر کردن درباره GitQL SDK

سوال اول این بود: چه چیزی می تواند بین GitQL و FileQL متفاوت باشد؟ بسته به این قسمت می تواند پویا باشد روی فرمت داده ها و روش خواندن آنها

پاسخ دو جزء بود. بیایید در بخش های بعدی به آنها بپردازیم.

اولین جزء، طرحواره داده است

در هر پرس و جوی SQL مانند، باید بررسی هایی را انجام دهیم تا مطمئن شویم همه چیز معتبر است. به عنوان مثال، در یک پرس و جو مانند SELECT UPPER(name), commit_count + 1 FROM branches، باید بررسی های زیر را انجام دهیم:

  • بررسی کنید که جدولی با شاخه های نام وجود دارد.
  • میدان name دارای نوع متن است تا بتوان آن را به تابع منتقل کرد UPPER بدون هیچ مشکلی.
  • میدان commit_count دارای نوع عدد صحیح است، به طوری که می توانیم آن را با عملگر پلاس و یک عدد صحیح دیگر استفاده کنیم.

اگر از نام جدول، نام فیلدها و انواع آن آگاه باشیم، می توان این بررسی ها را اجرا کرد. این اطلاعات در پروژه GitQL ثابت بود، اما اکنون، وقتی می‌خواهم آن را به یک SDK تبدیل کنم، باید آن را پویا کنم تا هر کاربر SDK بتواند بسته به آن، آن را تغییر دهد. روی داده های خودشان

پیشنهاد می‌کنیم بخوانید:  روش دسترسی به ایندکس در پایتون برای حلقه

بنابراین، من تمام اطلاعات مورد نیاز را در کامپوننتی به نام DataSchema کپسوله کردم و هنگامی که کاربر آن را به SDK ارسال کرد، همه بررسی ها به درستی کار خواهند کرد.

جزء دوم Data Provider است

هنگامی که کامپوننت DataSchema را تعریف کردیم تا انجام بررسی ها آسان تر شود روی داده ها، باید به سوال بعدی برویم: چگونه می توانیم داده ها را در اختیار موتور GitQL قرار دهیم؟

در GitQL، ما توابع ثابتی برای ارائه داده ها از فایل های .git داریم، اما در SDK، ما فقط با فایل های .git کار نمی کنیم و باید از کار با هر نوع داده ای پشتیبانی کنیم.

بنابراین، ایده این است که یک رابط بین موتور GitQL و کاربر SDK تعریف کنیم تا هر نوع داده ای را در قالب مورد نیاز برای موتور ارائه دهد. این کامپوننت DataProvider نام دارد و در قسمت بعدی جزئیات پیاده سازی را توضیح خواهم داد.

طراحی و پیاده سازی GitQL SDK

هدف این است که به کاربر SDK اجازه دهیم تعریف خود را از Data Schema و Provider منتقل کند و آنها را به راحتی با سایر اجزای GitQL مانند Tokenizer، Parser، Checker، Functions و Engine یکپارچه کند.

روش طراحی Data Schema

طرح داده باید شامل دو نوع اطلاعات باشد. اولاً باید جداول و نام فیلدهای صحیح را تعریف کند و ثانیاً انواع داده ها را برای آن فیلدها مشخص کند.

به عنوان مثال، در مورد FileQL، نام جدول و فیلد صحیح عبارتند از:

pub static ref TABLES_FIELDS_NAMES: HashMap<&'static str, Vec<&'static str>> = {
    let mut map = HashMap::new();
    map.insert(
        "files",
        vec!["path", "parent", "extension", "is_dir", "is_file", "size"],
    );
    map
};

در اینجا فقط یک جدول به نام تعریف می کنیم filesکه دارای شش فیلد است: path، parent، extension، is_dir، is_file، و size.

در نقشه دیگر، نوع داده صحیح را برای هر فیلد تعریف می کنیم. مثلا:

pub static ref TABLES_FIELDS_TYPES: HashMap<&'static str, DataType> = {
    let mut map = HashMap::new();
    map.insert("path", DataType::Text);
    map.insert("parent", DataType::Text);
    map.insert("extension", DataType::Text);
    map.insert("is_dir", DataType::Boolean);
    map.insert("is_file", DataType::Boolean);
    map.insert("size", DataType::Integer);
    map
};

سپس، یک نمونه از Schemaو با استفاده از دو نقشه آن را بسازید. باید آنها را به لیست نمونه Data Schema ارسال کند:

let schema = Schema {
    tables_fields_names: TABLES_FIELDS_NAMES.to_owned(),
    tables_fields_types: TABLES_FIELDS_TYPES.to_owned(),
};

روش طراحی ارائه دهنده داده

هدف مولفه Data Provider بارگذاری داده ها و نگاشت آنها در ساختار شی موتور GitQL است، بنابراین می توانیم آن را به عنوان یک رابط با یک تابع تعریف کنیم:

pub trait DataProvider {
    fn provide(
        &self,
        env: &mut Environment,
        table: &str,
        fields_names: &[String],
        titles: &[String],
        fields_values: &[Box<dyn Expression>],
    ) -> GitQLObject;
}

کاربر SDK می تواند این رابط را برای نوع داده های خود پیاده سازی کند و آن را با داده های مختلف کار کند.

پیشنهاد می‌کنیم بخوانید:  حلقه ها در پایتون

همچنین، می توانید کنترل کنید که به چه تعداد رشته نیاز دارید و چه پارامترهای اضافی را می خواهید. به عنوان مثال، در FileQL من آن را با نام پیاده سازی کردم FileDataProvider، و مسیر پایه را برای جستجو به عنوان پارامتر ارسال کرد.

شما همچنین می توانید آن را به هر شکلی اجرا کنید. مثلا، APIDataproviderو داده ها را از سرور بارگیری کرده و در آنها نقشه برداری کنید GitQLObject. شما همچنین می توانید به صورت پیاده سازی کنید LogDataProvider، و غیره روی. ایده اصلی یکسان است – فقط داده ها را در اختیار موتور قرار دهید.

روش استفاده از اجزای SDK با هم

پس از افزودن جعبه‌های GitQL SDK به پروژه و پیکربندی Data Schema و Provider برای داده‌های شما، می‌توانیم از GitQL SDK استفاده کنیم:

let mut env = Environment::new(schema);
let query = ...;

let mut reporter = DiagnosticReporter::default();
let tokenizer_result = tokenizer::tokenize(query.to_owned());
let tokens = tokenizer_result.ok().unwrap();
if tokens.is_empty() {
    return;
}

let parser_result = parser::parse_gql(tokens, &mut env);
if parser_result.is_err() {
    let diagnostic = parser_result.err().unwrap();
    reporter.report_diagnostic(&query, *diagnostic);
    return;
}

let query_node = parser_result.ok().unwrap();
let provider: Box<dyn DataProvider> = Box::new(FileDataProvider::new(base_path.to_owned()));
let evaluation_result = engine::evaluate(&mut env, &provider, query_node);

کد بالا پرس و جو را به عنوان یک رشته می گیرد و آن را پردازش می کند تا نتیجه ارزیابی را از موتور دریافت کند:

  • یک نمونه Environment با استفاده از DataSchema برای ردیابی انواع
  • ایجاد یک نمونه از DiagnosticEngine استفاده از آن برای گزارش خطا
  • کوئری را به توکنایزر ارسال کنید تا رشته را به لیستی از نشانه ها تبدیل کند.
  • لیست نشانه ها را به تجزیه کننده ارسال کنید تا به آن تبدیل شود TreeDataStructure.
  • یک نمونه از خود بسازید DataProvider و آن را با درخت به موتور منتقل کنید.
  • موتور نتیجه ارزیابی را که یک خطا یا داده است برمی گرداند.

این کامپوننت ها به جز Data Schema و Provider اصلا جدید نیستند و می توانید از خواندن جزئیات طراحی و پیاده سازی در مقاله اول لذت ببرید.

این تقریباً تمام چیزی است که برای انجام پروژه نیاز دارید، اما می‌توانید سفارشی‌سازی بیشتر و اجزای اضافی مانند آرگومان‌های CLI را اضافه کنید. نتیجه نهایی به این صورت خواهد بود:

fql_demo
نسخه ی نمایشی برای پروژه FileQL در حال اجرا روی فایل های محلی

می توانید کد منبع کامل را با تمام سفارشی سازی ها در مخزن FileQL پیدا کنید.

نتیجه

می توانید پروژه FileQL را به عنوان یک نمونه کامل که فقط در سه فایل ایجاد شده است بررسی کنید.

اگر پروژه را دوست داشتید، می توانید به آن ستاره بدهید ⭐ روی GitQL و FileQL

برای روش دانلود و استفاده از پروژه می توانید وب سایت را بررسی کنید روی سیستم عامل های مختلف

این پروژه هنوز انجام نشده است – این فقط شروع است. همه می توانند به این پروژه بپیوندند و به پروژه کمک کنند و ایده هایی را پیشنهاد کنند یا اشکالات را گزارش کنند.

با تشکر برای خواندن!