Overview
Στην Cosmos DB υπάρχουν ,όπως και στις relational databases, διαδικασίες που τις θεωρούμε και είναι server side και δεν είναι άλλες από τα user defined functions, τις stored procedures και τους triggers και ως προς την λογική και την χρήση τους είναι όμοιες.
Η βασική διαφορά είναι ο κώδικας που γράφουμε σε αυτές στην Cosmos DB είναι σε JavaScript (ECMA-262) και μέσα σε αυτές κάνουμε κλήσεις στο Cosmos DB SQL API αντί να γράφουμε T-SQL.
Όλες αυτές οι διαδικασίες υλοποιούνται σαν αντικείμενα στο συγκεκριμένο collection και αυτό σημαίνει ότι δεν μπορώ να έχω τέτοια που να μοιράζονται σε πολλαπλά collections.
Όπως και στις relational databases έτσι και στην Cosmos DB τα πλεονεκτήματα των udfs, sp, triggers είναι γνωστά και αφορούν encapsulation, atomic transactions και performance.
Κάθε server side operation τρέχει σε ένα δικό του environment (sandbox) και όταν ολοκληρώσει οι πόροι που δαπανούσε επιστρέφονται για χρήση σε άλλα.
Αν θέλουμε να έχουμε state μεταξύ server side operation αυτό θα πρέπει να το κρατάμε στην database.
Transactions
Σε κάθε stored procedure , trigger ο κώδικας που αυτές περιέχουν είναι ένα single logical transaction και όπως στις relational database έτσι και στην Cosmos DB υλοποιούνται τα ACID transactions properties.
Μάλιστα πρέπει να σημειωθεί ότι για να εξασφαλισθεί το consistency οι stored procedure / triggers εκτελούνται μόνο στην primary replica ενός Cosmos DB container.
Στην Cosmos DB οι stored procedures και οι triggers είναι ο μόνος τρόπος για να έχουμε multistatement transactions καθώς δεν υπάρχουν commands όπως BEGIN TRAN, COMMIT, ROLLBACK.
Όπως ανέφερα και παραπάνω κάθε stored procedure / trigger τρέχει στο δικό του sandbox με αυτό να σημαίνει ότι το κάθε transaction δεν μπορεί να έχει transaction scope εκτός από αυτό.
Με απλά λόγια αν έχω δύο triggers αυτοί δεν μπορούν να είναι στο ίδιο transaction.
Τα user defined functions δεν έχουν transactions καθώς εκτελούνται σαν μέρος ενός SELECT και σε αυτή την περίπτωση μπορώ να ορίσω μόνο το consistency level που επιθυμώ.
Πρέπει να τονισθεί ότι το κάθε transaction εφαρμόζεται σε ένα και μόνο ένα partition. Αυτό σημαίνει ότι αν έχω πολλαπλά partitions τότε θα πρέπει πχ στην εκτέλεση μιας stored procedure να έχω το partition key value για να μπορεί να ορισθεί το transaction scope.
JavaScript query API
Όπως αναφέρθηκε παραπάνω ο κώδικας που γράφω μέσα σε μια stored procedure / trigger / udf είναι javascript function.
Σε αυτή χρησιμοποιούμε το SQL API που μου παρέχει τα execution context, request, response και collection objects.
Η πρόσβαση σε αυτά γίνεται με buld-in objects/functions και είναι οι παρακάτω:
- getContext() - δίνει το context object με το οποίο έχω πρόσβαση σε όλες τις λειτουργίες που μπορώ να κάνω μέσα σε Cosmos DB database.
- getContext().getCollection() - επιστρέφει το collection στο οποίο δουλεύω
- getConext().getResponse() - επιστρέφει το response object.
- getConext().getRequest() - επιστρέφει το request object.
Ένα αρκετά περιεκτικό documentation για όσους έχουν την σχετική εμπειρία και απλά θέλουν γρήγορα να μπουν στο κλίμα μπορείτε να δείτε εδώ
How to create a stored procedure / trigger / udf
Η δημιουργία sp / trigger / udf γίνεται είτε μέσα από το Azure Cosmos DB portal είτε χρησιμοποιώντας το αντίστοιχο object του .NET SDK
Image 1 - Create SP/Trigger/UDF from Azure Cosmos DB Portal
var Proc = new StoredProcedure
{
Id = "procid",
Body = @"function procfnct(c) {...}"
};
Uri collectionUri = UriFactory.CreateDocumentCollectionUri (_dbName, _collectionName);
StoredProcedure createdStoredProcedure = await client.CreateStoredProcedureAsync (collectionUri, Proc );
Πρέπει να επισημανθεί ότι η αναφορά στο κάθε ένα από αυτά κατά την χρήση του γίνεται με το Id που έχουμε δώσει και όχι με το όνομα της εκάστοτε function.
Stored Procedures
Τυπικά μια stored procedure μπορεί να έχει input parameters και εκτελεί μια διαδικασία. Μπορεί να επιστρέψει αποτέλεσμα και για να το κάνει αυτό θα πρέπει να χρησιμοποιηθεί η getContext().getReponse().setBody(…)
Ένας τυπικός τρόπος εκτέλεσης μια sp είναι με την χρήση της ExecuteStoredProcedureAsync.
Triggers
Μοιάζουν με τις stored procedures αλλά δεν παίρνουν παραμέτρους ούτε επιστρέφουν κάτι.
Περιπτώσεις που χρησιμοποιούμε triggers είναι για data validation, transformation και auditing.
Επίσης όπως και στις relational database έχει μεγάλη σημασία ο κώδικας και το τι κάνουμε μέσα σε αυτές καθώς μπορεί να γίνουν αρκετά δαπανηρές σε πόρους εκτέλεσης.
Υπάρχουν pre και post triggers είναι κατ' αντιστοιχία οι instead of και after triggers σε SQL Server.
Τα inserted , deleted virtual tables που έχουμε στον SQL Server εδώ τα έχουμε μέσω των getContext().getRequest().getOperationType() function - επιστρέφει το operation που έκανε τον trigger να εκτεστεί (insert,delete,replace), getContext().getRequest().getBody() function - επιστρέφει το document που επεξεργαζόμαστε.
Αν έχω pre-trigger χρησιμοποιώ την getContext().getResponset() να κάνω τις αλλαγές μου.
Ένα τυπικός τρόπος για την εκτέλεση ενός trigger γίνεται με την χρήση των PreTriggerInclude/PostTriggerInclude request options της CreateDocumentAsync.
Αν δεν το κάνουμε αυτό τότε δεν θα εκτελεστεί κανένας καθώς στην Cosmos DB δεν έχουν την ίδια συμπεριφορά ως προς την εκτέλεση τους όπως στις relational databases.
User defined functions
Στην Cosmos DB μια udf είναι javascript function αλλά με μια σημαντική διαφορά, δεν έχουν access στο context object και χρησιμοποιούνται μόνο σε queries
Function VATPrice (Price) {
return Price * 1.24;
}
SELECT o.Price, udf.VATPrice(o.Price) FROM Orders as o
Προσοχή στην εκτέλεση καθώς πρέπει να χρησιμοποιηθεί το udf prefix/collection
//antonch