برنامه نویسی

کنترل با توابع PostgreSQL: بستن شکاف به عملکرد ORM

پرده برداری از تفاوت: درک شکاف بین درایور مستقیم و عملکرد ORM

زمانی که نوبت به انتخاب فناوری برای توسعه backend و دستکاری داده ها در پایگاه داده مانند PostgreSQL می رسد، توسعه دهندگان اغلب با انتخاب بین استفاده از درایور رسمی یا لایه انتزاعی مانند ORM، در این مورد، Prisma روبرو هستند. هر دو رویکرد مزایا و معایب خود را دارند، اما استدلال شده است که ORM ها به دلیل ویژگی های بی شماری که برای عملیات معمول CRUD که معمولاً در یک REST API انجام می شوند، برتر هستند. در این مقاله، بررسی خواهیم کرد که چگونه توابع در PostgreSQL به ما این امکان را می دهند که فاصله بین استفاده مستقیم از درایور رسمی و عملکردهای ارائه شده توسط یک ORM مانند Prisma را پر کنیم. ما به طور خاص بر روی عملیات درج داده تمرکز خواهیم کرد و بررسی خواهیم کرد که چگونه توابع می توانند نتایجی مشابه با نتایج بدست آمده در هنگام استفاده از یک ORM ارائه دهند، از جمله توانایی برگرداندن اشیاء ایجاد شده و موارد دیگر.

import 'dotenv/config';
import { Client } from 'pg';

const pg = new Client({
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT, 10),
  database: process.env.DB_NAME,
});

pg.connect().then(() => {
  console.log('db connected');
  pg.query(
    `INSERT INTO reservations (_date, hora, res_number, res_name, room, meal_plan, pax_number, cost, observations)
      VALUES
        ('2023-07-27', '21:00', 001, 'Jhon Doe', 'P01', 'SC', 2, 50.00, 'Sin observaciones')`,
  ).then((result) => {
    console.log(result);
    pg.end().then(() => {
      console.log('disconnected from db');
    });
  });
});

/*
db connected
Result {
  command: 'INSERT',
  rowCount: 1,
  oid: 0,
  rows: [],
  fields: [],
  ... more metada
}
disconnected from db
*/
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال که در Nodejs اجرا می شود، می بینید که اطلاعات مفید زیادی در مورد نتیجه آن عملیات وجود ندارد و ابرداده های زیادی وجود دارد.

حالا بیایید آن را با یک insert run در Prisma مقایسه کنیم.

prisma.pokemons.create({
    data:{
      id: 1100,
      name: 'new_pokemon'
    }
  }).then((result: any) => {
    console.log(result);
  })

/*
{ id: 1100, name: 'new_pokemon' }
*/
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با Prisma می توانیم از شر ابرداده خلاص شویم و شی درج شده در پایگاه داده را استخراج کنیم

بیایید سعی کنیم آن نوع نتیجه را تقلید کنیم.

مقدمه ای برای عملکرد در Postgresql.

بیایید مستقیماً به یک مثال کد ساده از یک تابع در Postgres برویم.

CREATE FUNCTION sum(num1 numeric, num2 numeric) -- declare function and parameteres
RETURNS numeric -- returned type
AS $$
BEGIN -- starting the code
  RETURN num1 + num2; -- result
END; -- end of code
$$ LANGUAGE plpgsql; -- end of the function

SELECT sum(2, 3); -- returns 5
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ممکن است بخش های بیشتری در اعلان تابع وجود داشته باشد، مانند گرفتن استثناها یا اعلام متغیرها. آن را برای بعد می گذاریم.

اما حالا بیایید به مثال دیگری بپردازیم. کمی مفصل تر

CREATE TABLE reservations (
  id SERIAL PRIMARY KEY,
  pax INTEGER,
  capacity INTEGER,
  do_res DATE, -- date of reservation
  to_res TIME -- time of reservation
);

-- populating database with bad data that will throw exceptions in our function
INSERT INTO reservations (pax, capacity, do_res, to_res) VALUES 
(0, 0, '2022-01-01', '10:00:00');

/* Use it to play with the function if needed
-- populating database with good data
INSERT INTO reservations (pax, capacity, do_res, to_res) VALUES 
(2, 4, '2022-01-01', '10:00:00'),
(3, 6, '2022-01-02', '11:00:00'),
(4, 8, '2022-01-03', '12:00:00'),
(5, 10, '2022-01-04', '13:00:00'),
(6, 12, '2022-01-05', '14:00:00'),
(7, 14, '2022-01-06', '15:00:00'),
(8, 16, '2022-01-07', '16:00:00'),
(9, 18, '2022-01-08', '17:00:00'),
(10, 20, '2022-01-09', '18:00:00');
*/
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید سعی کنیم تابعی ایجاد کنیم که بتواند درصد اشغال را مشخص کند.

SELECT SUM(pax)*100 / SUM(capacity) AS ocupation FROM reservations;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک پرس و جو عادی است اما اکنون اجازه دهید تابع را تولید کنیم

می‌دانم که می‌توانیم از یک NULLIF برای اجتناب از استثناها استفاده کنیم، اما می‌خواهم به زودی این استثنا را پرتاب کنم.

CREATE OR REPLACE FUNCTION porcentage_ocupation(fecha_i DATE, fecha_f DATE)
RETURNS FLOAT AS $$
DECLARE -- declaring variables
  total_pax INTEGER;
  total_capacity INTEGER;
  porcentage FLOAT;
BEGIN
  SELECT SUM(pax), SUM(capacity) INTO total_pax, total_capacity FROM reservations WHERE do_res BETWEEN fecha_i AND fecha_f;
  porcentage := total_pax * 100.0 / total_capacity;
  RETURN porcentage;
END;
$$ LANGUAGE plpgsql;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون ما یک تابع پیچیده تر ایجاد کرده ایم اما این هنوز کافی نیست

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

SELECT porcentage_ocupation('2022-01-01', '2022-12-31');
-- ERROR:  division by zero
-- CONTEXT:  PL/pgSQL function porcentaje_ocupation(date,date) line 8 at assignment
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من پیشنهاد نمی کنم که باید روش متفاوتی برای رسیدگی به این استثناها ایجاد کنیم، اما اگر بخواهیم می توانیم. در قسمت بعدی استثناها را مدیریت می کنیم و یک JSON را با داده ها و ابرداده هایی که می خواهیم از پایگاه داده ارائه کنیم، برمی گردانیم.

ارتقاء آن: افزایش قابلیت‌های عملکرد با پاسخ‌های سفارشی و مدیریت استثنا

و اکنون زمان آن است که برخی از ویژگی‌های دیگر را که می‌توانیم در Postgresql استفاده کنیم، مانند یک بلوک try/catch در postgresql و برخی از داده‌های سیستمی که می‌توانیم در داخل توابع دریافت کنیم، به شما ارائه کنیم.

CREATE OR REPLACE FUNCTION porcentage_ocupation2(fecha_i DATE, fecha_f DATE)
RETURNS JSON AS $$ -- returning a JSON is a great option to return customized results
DECLARE
  total_pax INTEGER;
  total_capacity INTEGER;
  porcentage FLOAT;
  stack text; -- will get the stack of errors here
  result JSON; -- will build the result here
BEGIN -- begin the function
  BEGIN -- this second begin works as the try in a try/catch block
    SELECT SUM(pax), SUM(capacity) INTO total_pax, total_capacity FROM reservations WHERE do_res BETWEEN fecha_i AND fecha_f;
    porcentage := total_pax * 100.0 / total_capacity;
    result := json_build_object('isError', FALSE, 'result', porcentage);
  EXCEPTION -- and here is the catch in the try/catch block
    WHEN OTHERS THEN
      GET STACKED DIAGNOSTICS stack = PG_EXCEPTION_CONTEXT; -- how we can obtain the stack of the error in postgresql
      result := json_build_object('isError', TRUE, 'message', SQLERRM, 'errorCode', SQLSTATE, 'stack', stack); -- SQLERRM, SQLSTATE are system variables provided by postgresql
  END; -- end of the try/catch block
  RETURN result; -- returning the response
END;
$$ LANGUAGE plpgsql;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و با ایجاد این تابع دوم، اجازه می‌دهیم نتیجه را وقتی به ۰ تقسیم کنیم، ببینیم.

SELECT porcentage_ocupation2('2022-01-01','2022-12-31');
                                                                      porcentage_ocupation2                                                
----------------------
 {"isError" : true, "message" : "division by zero", "errorCode" : "22012", "stack" : "PL/pgSQL function porcentage_ocupation2(date,date) line 11 at assignment"}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

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

pg.connect().then(() => {
  console.log('db connected');
  pg.query(`SELECT porcentage_ocupation2('2022-01-01','2022-12-31' as result)`).then(
    (result) => {
      console.log(result.rows[0].result); // looking for the result straight to the point it should be
      pg.end().then(() => {
        console.log('disconnected from db');
      });
    },
  );
});
/*
db connected
{
  isError: true,
  message: 'division by zero',
  errorCode: '22012',
  stack: 'PL/pgSQL function porcentage_ocupation2(date,date) line 11 at assignment'
}
disconnected from db
*/
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما از این واقعیت استفاده می کنیم که با بازگرداندن یک JSON از تابع Postgres، 1 و تنها 1 ردیف را برمی گرداند. به همین دلیل است که ما همیشه می توانیم نتیجه را در آن مکان از جسم جستجو کنیم. این دقیقا همان ویژگی است که ما می خواستیم بسازیم. حالا بیایید به جلو برویم تا آن را روی عملیات CRUD پیاده سازی کنیم، زیرا از انجام آن در SELECT QUERY معنادارتر است.

تبدیل مفاهیم به کد: نوشتن منطق INSERT و UPDATE در توابع PostgreSQL

ما در حال اتمام هستیم پس بیایید دیگر وقت را تلف نکنیم.

CREATE OR REPLACE FUNCTION create_reservation(
  _pax INTEGER,
  _capacity INTEGER,
  _do_res DATE,
  _to_res TIME
) RETURNS JSON AS $$
DECLARE
  inserted_reservation reservations; -- here will retrived the inserted object
  response JSON;
  stack_info TEXT;
BEGIN
  BEGIN
    IF _do_res < CURRENT_DATE THEN
      response := json_build_object(
        'isError', FALSE, -- It could be considered an error (a bad request error)
        'message', 'Bad request: No record inserted - You cant create a reservation in the past',
        'rowsAffected', 0,
        'result', NULL
      );
    ELSE
      BEGIN
        INSERT INTO reservations (
          pax,
          capacity,
          do_res,
          to_res
        ) VALUES (
          _pax,
          _capacity,
          _do_res,
          _to_res
        )
        RETURNING * INTO inserted_reservation; -- retriving the insert object

        IF inserted_reservation IS NULL THEN
          response := json_build_object(
            'isError', TRUE, 'message', 'No record inserted',
            'rowsAffected', 0
            );
        ELSE
          response := json_build_object(
            'isError', FALSE, 'result', inserted_reservation, 'rowsAffected', 1
            );
        END IF;
      EXCEPTION
        WHEN OTHERS THEN
          GET STACKED DIAGNOSTICS stack_info = PG_EXCEPTION_CONTEXT;
          response := json_build_object(
            'isError', TRUE, 'message', SQLERRM, 'errorCode', SQLSTATE,
            'stack', stack_info
            );
      END;
    END IF;

    RETURN response;
  END;
END;
$$ LANGUAGE plpgsql;

SELECT create_reservation(
  2, 4, '2023-07-27', '19:00'
) AS result;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به خاطر داشته باشید که این تابع یک “محدودیت” را به create_reservation اضافه می کند و اجازه ایجاد رزرو قبل از CURRENT_DATE را نمی دهد. این یک کنترل اضافی است که می توانیم با استفاده از توابع اضافه کنیم.

با این کار به پایان این پست رسیدیم.

در نتیجه، با استفاده از قدرت توابع در PostgreSQL، ما با موفقیت شکاف بین ORM و برنامه نویسی پایگاه داده مستقیم را کاهش دادیم. از طریق کاوش خود، ما به درک عمیق تری از نحوه استفاده موثر از توابع دست یافته ایم که به ما امکان می دهد پاسخ ها را سفارشی کنیم، استثناها را مدیریت کنیم و کنترل بیشتری در پایگاه داده خود اضافه کنیم.

در حالی که کار مستقیم با پایگاه داده ممکن است به تلاش بیشتری نیاز داشته باشد، کنترل بی نظیری بر فرآیند دستکاری داده ها ارائه می دهد. توابع به توسعه دهندگان قدرت می دهد تا کنترل دقیق داشته باشند و از پتانسیل کامل PostgreSQL استفاده کنند.

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

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا