Sage-Code Laboratory
index<--

Eve Database/SQL

Eve has basic interaction with relational databases. We design Eve to connect to different third party databases. The purpose of Eve to be versatile. It enables DML operations and direct SQL but not DCL and DDL operations.

Goals:

Page bookmarks:

definition

Models

Eve must read and update a database structure using an internal data model. A model is a map between Eve a relational data model and an object oriented model. This is called  ORM = Object Relational Model. An ORM model is connecting to one or multiple databases using vendor specific database modules.

Importing

The model library must be imported using these statements:

# using Oracle library
import
   db $evelib/db/core;
   orcl: $evelib/db/oracle;

Connection

One application can connect to multiple databases simultaneously. A specific kind of application called pipeline can pull data from multiple sources to update one target database or, spread data from one database and push data into multiple other databases.

pattern:


** create a database session
class OracleSession = {user,password,name: String} <: orcl.Database:
  new _credentials: String;
release
  orcl.disconnect();
return

constructor OracleSession(location,user,password:String) => (@self db.Session): 
  ** prepare credentials
  let self.location = location;
  let self.user = user;
  let self.password = password;
  ** prepare credentials
  let self.connect(self._credentials);
  orcl.connect();
return;

process:
  ** create database connection
  new demoDB =  set db := OracleSession(user:$user,password:$password,location:$location);
  ...
return;

Note: Usually a database has a security protocol based on user-name and password. These credentials can not be encoded in the Eve scripts, they need to be received as input parameters by the driver or set using configuration files that are secret, safly stored on the server. Once a connection is established it can be used to manipulate data, using Eve statements.

Database API

For each database kind we will create a library module. These modules will provide similar functionality for different databases. This basic functionality is created to make available a "Database Connector" in Eve scripts. We need this functions:

Functionality

Method Description
connect() Connect the database
disconnect() Disconnect from database
query() Direct SQL execution
execute() Execute a stored procedure
commit() Save all pending modifications
rollback() Rollback all pending modifications

Targeted Databases

Eve should provide drivers for main-stream databases:

Concepts

One database provide a complex framework to organize data in tables and documents. An application can read database structure and map it to an internal memory model. Then we can perform operations on this model: {search, append, update, delete}. The opperations are mirrored automaticly in the database.

Mapping

A database table has records. Eve strategy is to define one memory table as a collection of objects for each database table. The memory table is mapped to database table. Mapping is one to one. Field names are case sensitive.

Data cashing. The memory table do not store all the database data. Instead, the memory table store a small buffer. On rare ocasions, user can store all data in memory tables for small crytical tables to improve performance when a table is used very often. Usually these tables are metadata tables.

Tables

Internal memory tables are mixed collections of objects having same name as database tables. We can read a database table record by record on demand. A memory table can load one or multiple records in memory. Developer/user has full control over what data is stored in memory buffer, can refresh the buffer or can clear the buffer and load other data set with a different filter.

How to define a table:

A table is an smart class that knows it's bound to a database. Therefore we "inherit" a database table like it is a Eve class. EVE engine will bind the table to database. If the table in the database do not exist, EVE compiler will complain and will generate an error.


class TableName = {name: String} <: DB_Name.Table;

Records

Records are object instances representing in memory table structure. A record has a status property that can be used in conditionals. One record is the current record. Several records are cached in memory for a table. A record id identified by a numeric id. Records are "bound" to database data. A record has information about it's origin.


class RecordName = {id: Integer} <: Record;

Structure

Table structure is represented by Records structure. This can be flat or hierarchic. A Table is a collection of records. Tables can be related to each other. A record can contain a table of records. This is called nested table. Some databases have support for nested tables, some do not. In this case the nested table is just a relational table that has a foreign key to main table.

Note:

Sessions

Data model can work with sessions. A session have a name. A session can involve one or more databases. After a set of modifications is done you can commit changes in a session. If transaction fail all the modifications in session are reset. You can customize the actions in case of error. After session ends, you can create a report.

Operations

Any of the following operations can be included in a session:

Append

Eve can add new data records into one table using append() method of a table. This create new records.


routine update_tables(value1,value2 ...):
  ** create empty records
  new record1 := table_name1.append();
  new record2 := table_name2.append();
  ** populate records with values 
  
  update record1:
    let field_name := value1;
    ...
  update record2:
    let field_name := value2;
    ... 
  commit; 
return;

Update

Eve can do single or multiple row updates in one transaction. The Update need to find the record to update before is able to modify these records.

Single record:

** use search fields and values
process
  new record1 := table_name.find(field:value1, ...);
  new record2 := table_name.find(field:value2, ...);
  update record1:
    let field_name := value;
    ...
  update record2:
    let field_name := value;
    ...
  commit;
return;

Note A process or routine will start a new isolated transaction. In case of error, you can issue a rollback. The issue can be in the database, if you issue a rollback the database changes are not commited but the record remain modified in memory. You must refresh the record if you want the see the old values.

Delete

This statement will remove one or more records from a table.

Syntax


** Find one or more records and delete them
process
  ** local variables
  new deleted1 := table_name1.delete(field:value,...);
  new deleted2 := table_name2.delete(field:value,...);
  commit;
finalize
  ** check status
  expect deleted1 >  0;
  expect deleted2 >= 0;
  ** print status
  print (deleded1, "records deleted")
  print (deleded2, "records deleted")
return;

Bulk Operations

For bulk operations there are 2 methods. Using Eve cycle or using direct SQL.


** using a loop to create a single session  
routine delete_records(value):
  ** delete current record using: loop
  for record in table_name loop
    record.delete() if record.field_name == value;
  repeat;  
  commit;
return;

Direct SQL

Sometimes we need to bypass the ORM and execute native SQL:


# execute query string and return a list of records
  new query_result := db.query(query_template ? source)

Working with Direct SQL is not our purpose. Eve has enaugh power to enable developers handle processes. Using direct SQL is a backup, offered for special opperations Eve is not capable of. Examples will be provided for testing.

Procedures

Some databases have support for stored procedures. It will be a challenge to engage these procedures from EVE. Since not all databases have stored procedures, these are extra features that we can implement in Eve. Most of the time, Eve scripts are self contained and depend only on database row data and do not use extra layers like views, snapshots or stored procedures to read data. However just in case data is not available in any other way we can call stored procedures to retrive data.


driver main:

** prepare an object (not updatable)
class Result = {
      field_name1:Type ,
      field_name2:Type ,
      ...
      } <: Record;

global
  new buffer: ()Result; -- collection/table
process
  ** execute stored procedure and collect records in the buffer
  let buffer <+ db.procedure_name(arguments);
  ...
return;

Introspection

For debugging the SQL, Eve will enable introspection.


Read next: Eve Tutorial