รู้จักกับ plv8: เมื่อ Postres ต้องมาเชื่อมจิตกับ JavaScript
Published: 2024-07-16 09:41:17

##บทนำ##
วันก่อนไปส่อง [facebook fanpage ขาเดฟ](https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fkhadev%2Fposts%2Fpfbid0h5iDDRpqpnrSeUTwJvmPapsKRjBBrbHmepK8fSHDKMxhbtYAfu9fF145QNMcQYs4l&show_text=true&width=500" width="500" height="397" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowfullscreen="true" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share) ซึ่งเป็นแหล่งร่วมตัวเทพ ในวงการ developer ได้โพสเกี่ยวกับส่วนขยาย(extension) ของ postgres ที่ชื่อว่า plv8 ที่ช่วยให้คุณสามารถเขียนฟังก์ชันและทริกเกอร์ด้วยภาษา JavaScript ได้ โดยใช้ V8 JavaScript engine ที่พัฒนาโดย Google

##แล้วมันดียังไง##
- **ใช้ JavaScript ในฐานข้อมูล:** เขียนโค้ด JavaScript ที่ทำงานโดยตรงใน PostgreSQL
- **ประสิทธิภาพสูง:** V8 engine มีประสิทธิภาพสูง ทำให้การประมวลผลเร็วกว่าภาษาอื่นๆ เช่น PL/pgSQL
- **ความยืดหยุ่น:** สามารถใช้คุณสมบัติของ JavaScript เช่น closures, high-order functions ได้
- **JSON processing:** เหมาะสำหรับการทำงานกับข้อมูล JSON ใน PostgreSQL
- **NPM packages:** สามารถใช้ JavaScript libraries จาก NPM ได้

##การใช้งาน##
-ติดตั้ง plv8 extension:
```sql
CREATE EXTENSION plv8
```
- การสรา้งและเรียกใช้ฟังก์ชั่น:
```sql
--สรา้งฟังก์ชั่น hellow_world ในการคืนข้อความ "Hello, World!"
CREATE FUNCTION hello_world() RETURNS text AS $$
    return "Hello, World!";
$$ LANGUAGE plv8;

--การเรียกใช้ function hello_world--
SELECT hello_world();
```

- ฟังก์ชันที่ทำงานกับ JSON และใช้ JavaScript Array methods:
```sql
CREATE OR REPLACE FUNCTION filter_and_transform_users(users json) RETURNS json AS $$
    const userArray = JSON.parse(users);
    const result = userArray
        .filter(user => user.age > 18)
        .map(user => ({
            fullName: `${user.firstName} ${user.lastName}`,
            isAdult: user.age >= 21,
            emailDomain: user.email.split('@')[1]
        }));
    return JSON.stringify(result);
$$ LANGUAGE plv8;

-- ตัวอย่างการใช้งาน
SELECT filter_and_transform_users('[
    {"firstName": "John", "lastName": "Doe", "age": 25, "email": "john@example.com"},
    {"firstName": "Jane", "lastName": "Smith", "age": 17, "email": "jane@test.com"},
    {"firstName": "Bob", "lastName": "Johnson", "age": 30, "email": "bob@example.com"}
]');
```
- ฟังก์ชันที่ใช้ closure และ state ระหว่าง invocations:
```sql
CREATE OR REPLACE FUNCTION create_counter() RETURNS json AS $$
    const state = plv8.initialize_state();
    if (!state.counter) state.counter = 0;
    
    function increment() {
        state.counter++;
        return state.counter;
    }
    
    function decrement() {
        state.counter--;
        return state.counter;
    }
    
    function reset() {
        state.counter = 0;
        return state.counter;
    }
    
    return JSON.stringify({increment, decrement, reset});
$$ LANGUAGE plv8;

-- ตัวอย่างการใช้งาน
DO $$
    const counter = JSON.parse(plv8.execute('SELECT create_counter()')[0].create_counter);
    plv8.elog(NOTICE, counter.increment()); // 1
    plv8.elog(NOTICE, counter.increment()); // 2
    plv8.elog(NOTICE, counter.decrement()); // 1
    plv8.elog(NOTICE, counter.reset()); // 0
$$ LANGUAGE plv8;
```
- ฟังก์ชันที่ใช้ external library (ต้องติดตั้ง library ก่อน):
```sql
CREATE OR REPLACE FUNCTION validate_email(email text) RETURNS boolean AS $$
    const validator = require('email-validator');
    return validator.validate(email);
$$ LANGUAGE plv8;

-- ตัวอย่างการใช้งาน
SELECT validate_email('user@example.com');
SELECT validate_email('invalid-email');
```
- ทริกเกอร์ที่ใช้ PL/V8:
```sql
-- สรา้งตารางเก็บชื่อผู้ใช้งาน 
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username TEXT,
    email TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

--สร้างฟังก์ชั่น validate_user() ในการตรวจสอบชื่อผู้ใช้งาน และ email
CREATE OR REPLACE FUNCTION validate_user() RETURNS trigger AS $$
    if (!NEW.username || NEW.username.length < 3) {
        plv8.elog(ERROR, 'Username must be at least 3 characters long');
    }
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(NEW.email)) {
        plv8.elog(ERROR, 'Invalid email format');
    }
    return NEW;
$$ LANGUAGE plv8;

--สร้าง trigger ในการเรียกใช้ฟังก์ชั่น validate_user() ในการตรวจสอบชื่อผู้ใช้งาน และ email
CREATE TRIGGER user_validation_trigger
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION validate_user();

-- ตัวอย่างการใช้งาน
INSERT INTO users (username, email) VALUES ('jo', 'john@example.com'); -- จะ error
INSERT INTO users (username, email) VALUES ('john', 'invalid-email'); -- จะ error
INSERT INTO users (username, email) VALUES ('john', 'john@example.com'); -- จะสำเร็จ
```
##มีอะไรต้องระวังไหม##
- ต้องติดตั้ง PL/V8 extension เพิ่มเติม
- การใช้ JavaScript อาจทำให้การ debug ยากขึ้น
- ต้องระวังเรื่องความปลอดภัย เพราะ JavaScript สามารถเข้าถึงระบบไฟล์ได้

##ส่งท้าย###

ส่วนขยาย plv8 ช่วยให้ทาง developer สามารถสร้างฟังก์ชั่นที่ทำงานเฉพาะ ซึ่งฝั่ง developer มีความชำนาญ JavaScript อยู่แล้วโดยไม่ต้องพึ่ง Database Admin (ซึ่งส่วนใหญ่จะไม่ค่อยชำนาญภาษา pl/pgsql) อีกทั้งยังสามารถเรียก  npm เพื่อเพิ่มความสามารถผ่าน javscript อย่างที่ผมเคยบอกว่า postgres เป็นทุกอย่างให้เธอแล้ว