diff --git a/db-23ai-fundamentals/javascript/images/im10.png b/db-23ai-fundamentals/javascript/images/im10.png new file mode 100644 index 000000000..8f76a49c4 Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im10.png differ diff --git a/db-23ai-fundamentals/javascript/images/im11.png b/db-23ai-fundamentals/javascript/images/im11.png new file mode 100644 index 000000000..58079f14f Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im11.png differ diff --git a/db-23ai-fundamentals/javascript/images/im12.png b/db-23ai-fundamentals/javascript/images/im12.png new file mode 100644 index 000000000..aacf4471b Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im12.png differ diff --git a/db-23ai-fundamentals/javascript/images/im13.png b/db-23ai-fundamentals/javascript/images/im13.png new file mode 100644 index 000000000..15ea7eedf Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im13.png differ diff --git a/db-23ai-fundamentals/javascript/images/im14.png b/db-23ai-fundamentals/javascript/images/im14.png new file mode 100644 index 000000000..81ec4fce5 Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im14.png differ diff --git a/db-23ai-fundamentals/javascript/images/im15.png b/db-23ai-fundamentals/javascript/images/im15.png new file mode 100644 index 000000000..d597915e6 Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im15.png differ diff --git a/db-23ai-fundamentals/javascript/images/im7.png b/db-23ai-fundamentals/javascript/images/im7.png new file mode 100644 index 000000000..2555e822b Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im7.png differ diff --git a/db-23ai-fundamentals/javascript/images/im8.png b/db-23ai-fundamentals/javascript/images/im8.png new file mode 100644 index 000000000..ad22d33db Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im8.png differ diff --git a/db-23ai-fundamentals/javascript/images/im9.png b/db-23ai-fundamentals/javascript/images/im9.png new file mode 100644 index 000000000..dd47a83b3 Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/im9.png differ diff --git a/db-23ai-fundamentals/javascript/images/simple-db-actions.png b/db-23ai-fundamentals/javascript/images/simple-db-actions.png new file mode 100644 index 000000000..0022fa3c4 Binary files /dev/null and b/db-23ai-fundamentals/javascript/images/simple-db-actions.png differ diff --git a/db-23ai-fundamentals/javascript/javascript.md b/db-23ai-fundamentals/javascript/javascript.md index 2124815b9..f7591ff4d 100644 --- a/db-23ai-fundamentals/javascript/javascript.md +++ b/db-23ai-fundamentals/javascript/javascript.md @@ -1,225 +1,327 @@ -# JavaScript Stored Procedures +# Using JavaScript Stored Procedures in Oracle Database 23ai ## Introduction -In this workshop, you will learn server-side programming using PL/SQL and JavaScript in Oracle Database 23ai. Oracle Database's support for various programming languages enables developers to build complex business logic directly within the database, helping you create efficient and scalable applications. +Welcome to JavaScript Stored Procedures in Oracle Database 23ai! Imagine you're building a small online bookstore. Traditionally, you'd need to write all your business logic (like calculating prices, discounts, and shipping) in PL/SQL. But what if you're more comfortable with JavaScript? Good news! With Oracle Database's Multilingual Engine (MLE), you can now write this logic in JavaScript and run it directly inside the database. -Estimated Lab Time: 25 minutes +Think of it like having a JavaScript engine inside your database. Instead of sending data back and forth between your application and the database, you can process it right where it lives - in the database itself. This makes your applications faster and simpler to maintain. -### Objective: -The objective of this workshop is to familiarize you with the server-side programming features of the Oracle Database, focusing on PL/SQL and JavaScript. By the end of this workshop, you will be able to create and execute stored procedures using PL/SQL and JavaScript. +Estimated Lab Time: 15 minutes + +### Objectives: +* Create JavaScript stored procedures for bookstore calculations +* Work with JavaScript modules for price processing +* Run dynamic JavaScript code for testing +* Look at basic debugging techniques ### Prerequisites: -- Access to Oracle Database 23ai. -- Basic understanding of SQL is helpful. +* Access to Oracle Database 23ai +* Basic understanding of JavaScript and SQL -## Task 1: Lab setup +## Task 1: Creating Your First JavaScript Stored Procedure -1. From the Autonomous Database home page, **click** Database action and then **click** SQL. - ![click SQL](images/im1.png " ") - -2. Let's create some tables to use in the lab. Copy and run the following SQL script: +1. In this task, we'll create a simple function to calculate book discounts. Let's break down what we're doing and why each step matters. + + First, let's access our SQL workspace. If you haven't done so already, from the Autonomous Database home page, **click** Database Actions and then **click** SQL. + ![click SQL](images/im1.png =50%x*) - ``` - - DROP TABLE IF EXISTS employees cascade constraints; - DROP TABLE IF EXISTS tasks cascade constraints; - - -- Create a table to store employee data - CREATE TABLE employees ( - employee_id INT PRIMARY KEY, - first_name VARCHAR(50), - last_name VARCHAR(50), - department VARCHAR(50) - ); + Using the ADMIN user isn’t typically advised due to the high level of access and security concerns it poses. **However**, for this demo, we’ll use it to simplify the setup and ensure we can show the full range of features effectively. - -- Create a table to store task data - CREATE TABLE tasks ( - task_id INT PRIMARY KEY, - employee_id INT, - task_description VARCHAR(255), - due_date DATE, - FOREIGN KEY (employee_id) REFERENCES employees(employee_id) - ); +2. Before we begin, this lab will be using Database Actions Web. If you're unfamiliar, please see the picture below for a simple explanation of the tool. You can click on the photo to enlarge it. + + ![click SQL](images/simple-db-actions.png =50%x*) + +3. Now, let's create our first JavaScript function. We'll start with something simple - calculating a discounted book price: + + ``` + + create or replace mle module bookstore_module + language javascript as + /** + * Calculates the final price after discount + * @param {number} price - Original book price + * @param {number} discountPercent - Discount percentage (e.g., 20 for 20% off) + * @returns {number} The final price after discount + */ + function calculateDiscountedPrice(price, discountPercent) { + const discount = price * (discountPercent / 100); + return price - discount; + } + export { calculateDiscountedPrice } + / ``` - ![insert some books](images/im2.png " ") -## Task 2: Creating and Using PL/SQL Stored Procedures + Let's break this down: + - `CREATE OR REPLACE MLE MODULE` tells Oracle to create a new JavaScript module (or replace it if it already exists) + - The function takes two parameters: the original price and the discount percentage + - We calculate the discount amount and subtract it from the original price + - The `export` statement makes our function available to be called from SQL and PL/SQL + + ![click SQL](images/im7.png =50%x*) -1. PL/SQL (Procedural Language/SQL) is Oracle's procedural extension for SQL. It allows you to write complex scripts that include variables, control structures, and SQL statements. Let's create a simple stored procedure to insert a new employee into the employees table. +4. Now we need to create a way to call our JavaScript function from SQL. This is called a "wrapper function": ``` - DROP procedure if exists add_employee; - - CREATE OR REPLACE PROCEDURE add_employee( - p_employee_id IN employees.employee_id%TYPE, - p_first_name IN employees.first_name%TYPE, - p_last_name IN employees.last_name%TYPE, - p_department IN employees.department%TYPE - ) AS - BEGIN - INSERT INTO employees (employee_id, first_name, last_name, department) - VALUES (p_employee_id, p_first_name, p_last_name, p_department); - END; + create or replace function get_final_price( + price number, + discount number + ) return number as + mle module bookstore_module + signature 'calculateDiscountedPrice'; + / ``` - ![insert some books](images/im3.png " ") -2. Execute the stored procedure to add a new employee: + This wrapper acts as a connection between SQL and JavaScript: + - It takes the same parameters as our JavaScript function + - `MLE MODULE bookstore_module` tells Oracle which JavaScript module to use + - `SIGNATURE 'calculateDiscountedPrice'` says which function to call in that module + + ![wrapper](images/im8.png =50%x*) + + +5. Now comes the fun part - testing our function. Let's calculate the price of a $20 book with a 20% discount: ``` - BEGIN - add_employee(1, 'John', 'Doe', 'IT'); - add_employee(2, 'Jane', 'Smith', 'HR'); - END; - / + select get_final_price(20, 20) as final_price; ``` - ![insert more employees](images/im4.png " ") -3. Add some tasks to our task table for addition context. + The function took our $20 book, applied a 20% discount ($4 off), and returned the final price of $16. + + ![wrapper](images/im9.png =50%x*) + + +## Task 2: Working with JavaScript Modules + +Now that we understand basic functions, let's take it a step further. We'll add shipping calculations and show how different JavaScript modules can work together. + +1. First, we need to set up an environment that tells the database how our modules will work together: ``` - INSERT INTO tasks (task_id, employee_id, task_description, due_date) - VALUES - (1, 1, 'Complete project report', TO_DATE('2024-06-01', 'YYYY-MM-DD')), - (2, 2, 'Organize team meeting', TO_DATE('2024-06-05', 'YYYY-MM-DD')), - (3, 1, 'Update software documentation', TO_DATE('2024-06-10', 'YYYY-MM-DD')); + create or replace mle env bookstore_env + imports ( + 'bookstore_module' module bookstore_module + ); ``` - ![insert more employees](images/im5.png " ") + This tells JavaScript where to find different pieces of code. Here, we're saying "when code asks for 'bookstore_module', use the module we created earlier." -## Task 3: Integrating JavaScript + ![wrapper](images/im10.png =50%x*) -1. With Oracle Database 23ai, you can write and execute JavaScript code directly within the database using the Multilingual Engine (MLE). Let's create a simple JavaScript module to process employee tasks. +2. Now let's create a new module that handles shipping calculations: ``` - DROP MLE MODULE IF EXISTS task_module; - CREATE MLE MODULE IF NOT EXISTS task_module - LANGUAGE JAVASCRIPT AS + create or replace mle module shipping_module + language javascript as + import * as bookstore from "bookstore_module"; /** - * Calculate the number of days left until the due date of a task - * @param {string} dueDate - The due date of the task in YYYY-MM-DD format - * @returns {number} - */ - function daysUntilDue(dueDate) { - const due = new Date(dueDate); - const today = new Date(); - today.setHours(0, 0, 0, 0); // Normalize to midnight - const timeDiff = due - today; // Difference in milliseconds - return Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); // Convert to days + * calculates total cost including shipping + * @param {number} price - book price + * @param {number} discount - discount percentage + * @param {number} weight - book weight in pounds + * @returns {number} total cost including shipping + */ + export function calculateTotalWithShipping(price, discount, weight) { + const baseShippingRate = 2; // $2 base rate + const pricePerPound = 1.5; // $1.50 per pound + + // First calculate the discounted price using our previous function + const finalPrice = bookstore.calculateDiscountedPrice(price, discount); + + // Calculate shipping based on weight + const shippingCost = baseShippingRate + (weight * pricePerPound); + + // Log the breakdown for transparency + console.log(`Book price after discount: $${finalPrice}`); + console.log(`Shipping cost: $${shippingCost}`); + + return finalPrice + shippingCost; } - - export { daysUntilDue } - /; - + / ``` - ![insert more employees](images/im6.png " ") + Let's examine what's happening here: + - `import * as bookstore` lets us use functions from our previous module + - We define shipping costs: $2 base rate plus $1.50 per pound + - We first calculate the discounted price using our previous function + - Then we calculate shipping based on the book's weight + - `console.log` prints information we can see when running the function + - Finally, we return the total cost (discounted price + shipping) + + ![wrapper](images/im11.png =50%x*) -2. Create a PL/SQL function to call the JavaScript module: + +3. Just like before, we need to create a wrapper function to call our shipping calculator from SQL: ``` - CREATE FUNCTION days_until_due_date( - p_due_date VARCHAR2 - ) RETURN NUMBER - AS - MLE MODULE task_module - SIGNATURE 'daysUntilDue(string)'; + create or replace function get_total_with_shipping( + price number, + discount number, + weight number + ) return number as + mle module shipping_module + env bookstore_env + signature 'calculateTotalWithShipping'; / ``` - ![insert more employees](images/table-value-5.png " ") -3. Use the function to check the number of days until a task is due: + Notice something new here? We added `ENV bookstore_env` - this tells Oracle to use the environment we created that knows about our modules. + ![wrapper](images/im12.png =50%x*) + + +4. Let's test our shipping calculator with a real example - a $20 book that's 20% off and weighs 2 pounds: + ``` - SELECT - task_description, - days_until_due_date(TO_CHAR(due_date, 'YYYY-MM-DD')) AS days_left - FROM tasks; + select get_total_with_shipping(20, 20, 2) as total_cost; ``` -## Task 4: Managing Data with JavaScript + Let's break down what happened: + - original price: $20 + - after 20% discount: $16 + - shipping: $2 base + (2 pounds × $1.50) = $5 + - total cost: $16 + $5 = $21 + + ![wrapper](images/im13.png =50%x*) + + +## Task 3: Dynamic JavaScript Execution -1. JavaScript in Oracle Database also supports inline functions for quick and simple operations. Let's create an inline function to concatenate task descriptions. +Sometimes you want to try out JavaScript code quickly without creating permanent modules. Oracle 23ai provides the DBMS_MLE package for this purpose. +1. Let's try some quick price calculations for different books: ``` - CREATE FUNCTION concatenate_tasks( - p_task1 VARCHAR2, - p_task2 VARCHAR2 - ) RETURN VARCHAR2 - AS - MLE LANGUAGE JAVASCRIPT - q'~ - return p_task1 + ' and ' + p_task2; - ~'; + declare + l_ctx dbms_mle.context_handle_t; + begin + dbms_output.enable(null); + l_ctx := dbms_mle.create_context(); + + dbms_mle.eval( + context_handle => l_ctx, + language_id => 'JAVASCRIPT', + source => q'~ + // Create an array of books with prices and discounts + const books = [ + {name: "JavaScript Guide", price: 29.99, discount: 15}, + {name: "Database Basics", price: 24.99, discount: 10} + ]; + + // Calculate final price for each book + books.forEach(book => { + const finalPrice = book.price * (1 - book.discount/100); + console.log(`${book.name}: $${finalPrice.toFixed(2)} (${book.discount}% off)`); + }); + ~' + ); + + dbms_mle.drop_context(l_ctx); + end; / ``` - ![insert more employees](images/table-value-6.png " ") + Let's understand what's happening here: + - `dbms_mle.create_context()` creates a temporary javascript environment + - we use the q'~...~' syntax to write multiple lines of javascript + - we create an array of books, each with a name, price, and discount + - the foreach loop processes each book and calculates its final price + - `tofixed(2)` formats the price to show exactly 2 decimal places + - finally, we clean up by dropping the context + ![wrapper](images/im14.png =50%x*) -2. Use the inline function to concatenate task descriptions: - ``` - - SELECT - concatenate_tasks('Complete report', 'Attend meeting') AS combined_tasks ; - - ``` - ![insert more employees](images/table-value-6.png " ") +## Task 4: Simple Debugging -## Task 5: Merging Data with JavaScript Modules +When writing JavaScript code, things don't always work perfectly the first time. Let's learn how to add debug messages to help us understand what's happening in our code. -1. JavaScript modules can be particularly useful for merging and transforming data within the Oracle Database. Suppose we have a new set of task data to merge into the tasks table. +1. Here's an example that shows step-by-step what's happening in our calculations: ``` - MERGE INTO tasks t - USING ( - VALUES - (1, 'Prepare presentation', '2024-05-20'), - (2, 'Review budget', '2024-05-22') - ) src (task_id, task_description, due_date) - ON (t.task_id = src.task_id) - WHEN MATCHED THEN - UPDATE SET - t.task_description = src.task_description, - t.due_date = TO_DATE(src.due_date, 'YYYY-MM-DD') - WHEN NOT MATCHED THEN - INSERT (task_id, task_description, due_date) - VALUES (src.task_id, src.task_description, TO_DATE(src.due_date, 'YYYY-MM-DD')); + declare + l_ctx dbms_mle.context_handle_t; + begin + dbms_output.enable(null); + l_ctx := dbms_mle.create_context(); + + dbms_mle.eval( + context_handle => l_ctx, + language_id => 'JAVASCRIPT', + source => q'~ + function calculateOrder(bookPrice, quantity, discountPercent) { + // Start with basic information + console.log(`Debug: Starting calculation for order...`); + console.log(`Debug: Input values:`); + console.log(` - Book price: $${bookPrice}`); + console.log(` - Quantity: ${quantity}`); + console.log(` - Discount: ${discountPercent}%`); + + // Calculate subtotal + const subtotal = bookPrice * quantity; + console.log(`Debug: Subtotal (${bookPrice} × ${quantity}): $${subtotal}`); + + // Calculate discount amount + const discount = subtotal * (discountPercent / 100); + console.log(`Debug: Discount calculation:`); + console.log(` - ${discountPercent}% of $${subtotal}`); + console.log(` - Discount amount: $${discount}`); + + // Calculate final price + const final = subtotal - discount; + console.log(`Debug: Final price: $${final}`); + return final; + } + + // Test the function with a real order + console.log('Testing order calculation...'); + calculateOrder(19.99, 3, 15); + ~' + ); + + dbms_mle.drop_context(l_ctx); + end; / ``` - ![insert more employees](images/table-value-6.png " ") + ![wrapper](images/im15.png =50%x*) + + These debug messages help you: + - Verify that your function received the right values + - Check the calculation steps are working like you expect them to + - Find where things might be going wrong if you get unexpected results -2. In this lab, we explored the powerful capabilities of server-side programming in Oracle Database, focusing on PL/SQL and JavaScript. We learned how to create and execute stored procedures, integrate JavaScript with the database, and manage data efficiently using both PL/SQL and JavaScript. These skills are essential for developing robust and scalable applications that leverage the full power of the Oracle Database. +2. In this workshop, you've learned how to: + 1. Create JavaScript functions that run inside the database + 2. Connect JavaScript modules together to build more complex functionality + 3. Run quick JavaScript tests using dynamic execution + 4. Add debug messages to help troubleshoot your code ## Learn More -* [Oracle Database 23ai Documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/server-side-programming.html#GUID-C05228D2-249D-405C-A65A-9039E76C028E) -* [Blog - Introduction to JavaScript in Oracle Database 23ai](https://blogs.oracle.com/developers/post/introduction-javascript-oracle-database-23c-free-developer-release?source=:so:li:or:awr:odb:::) +* [JavaScript Programming in Oracle Databas](https://docs.oracle.com/en/database/oracle/oracle-database/23/mlejs/index.html) +* [DBMS_MLE Package Documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_MLE.html) ## Acknowledgements * **Author** - Killian Lynch, Database Product Management -* **Contributors** - Dom Giles, Distinguished Database Product Manager -* **Last Updated By/Date** - Killian Lynch, April 2024 \ No newline at end of file +* **Contributors** +* **Last Updated By/Date** - Killian Lynch, December 2024 diff --git a/db-23ai-fundamentals/workshops/sandbox/manifest.json b/db-23ai-fundamentals/workshops/sandbox/manifest.json index 37bafe654..84cd35585 100644 --- a/db-23ai-fundamentals/workshops/sandbox/manifest.json +++ b/db-23ai-fundamentals/workshops/sandbox/manifest.json @@ -54,6 +54,12 @@ "type": "livelabs", "filename": "../../new-json-schema/new-json-schema.md" }, + { + "title": "Lab: JavaScript Stored Procedures", + "description": "Creating avaScript Stored Procedures", + "type": "livelabs", + "filename": "../../javascript/javascript.md" + }, { "title": "High Availability", "description": "Talks about some of the new HA features in 23ai", diff --git a/db-23ai-fundamentals/workshops/tenancy/manifest.json b/db-23ai-fundamentals/workshops/tenancy/manifest.json index cb1fb1644..c50e16c18 100644 --- a/db-23ai-fundamentals/workshops/tenancy/manifest.json +++ b/db-23ai-fundamentals/workshops/tenancy/manifest.json @@ -61,6 +61,12 @@ "type": "livelabs", "filename": "../../new-json-schema/new-json-schema.md" }, + { + "title": "Lab: JavaScript Stored Procedures", + "description": "Creating avaScript Stored Procedures", + "type": "livelabs", + "filename": "../../javascript/javascript.md" + }, { "title": "High Availability", "description": "Talks about some of the new HA features in 23ai",