dotConnect Universal Documentation
UniSQL

One of the most crucial problems in programming applications for several databases is that SQL syntax can be different in many situations. This article demonstrates how dotConnect Universal helps to overcome this issue.

Database applications operate data using SQL statements. Unless entered directly by the user, the statements can be constructed in one of two ways, either hard-coded during development, or constructed at run time. The first way is very convenient for developer, while the second way is far more flexible. dotConnect Universal allows to take best from both approaches: you can hard-code SQL statements that are transformed into appropriate syntax in run time.

General Information

Universal capabilities of dotConnect Universal are based on the following features:

Knowing this, you can write truly database-independent SQL code interpreted in run time.

Macros and Conditions

Macro is a set of name, condition and value. Macro evaluates to its value if underlying condition is enabled, or to empty string if the condition is not enabled. Conditions are enabled or disabled depending on DBMS server the UniConnection is connected to, and data provider used. For example, if you connect to Oracle server using dotConnect for Oracle, the following conditions will be enabled:

  1. Oracle - because Oracle server is used.
  2. OraDirect - because dotConnect for Oracle is used.

Consequently, all macros that base on Oracle and OraDirect conditions return their value when used in SQL statements; all other macros return empty string.

For list of available conditions (in other words, predefined macros) refer to the Macros Reference.

From API point of view, macros are represented as UniMacro class. Collections of macros are organized into UniMacroCollection, which can be accessed through Macros property of UniConnection. Each connection has individual set of macros.

The following examples demonstrate usage of macros:

UniConnection conn = new UniConnection();
//Connecting to MySQL server through dotConnect for MySQL
conn.ConnectionString = "provider=MySQL;Password=root;User Id=root;Host=localhost;Port=3307;";
//Define macro for table dept in database test
UniMacro mac = new UniMacro("tablename", "test.dept", "MySQL");
conn.Macros.Add(mac);
//Define query that uses the macro
UniCommand cmd = new UniCommand("SELECT Count(*) FROM {tablename}");
cmd.Connection = conn;
conn.Open();
try {
  //Run the query and make sure it is executed correctly
  string response = cmd.ExecuteScalar().ToString();
  MessageBox.Show(response);
}
finally {        
  conn.Close();
}
Dim conn As New UniConnection()
'Connecting to MySQL server through dotConnect for MySQL
conn.ConnectionString = "provider=MySQL;Password=root;User Id=root;Host=localhost;Port=3307;"
'Define macro for table dept in database test
Dim mac As New UniMacro("tablename", "test.dept", "MySQL")
conn.Macros.Add(mac)
'Define query that uses the macro
Dim cmd As New UniCommand("SELECT Count(*) FROM {tablename}")
cmd.Connection = conn
conn.Open()
Try
  'Run the query and make sure it is executed correctly
  Dim response As String = cmd.ExecuteScalar().ToString()
  MessageBox.Show(response)
Finally
  conn.Close()
End Try

Now suppose we need to do the same on an Oracle server. Due to usage of UniSQL the only thing to add is another macro:

UniConnection conn = new UniConnection();
//Connecting to Oracle server through dotConnect for Oracle
conn.ConnectionString = "provider=Oracle;Password=tiger;User Id=Scott;Host=ora920;";
//Define macros for tables dept
UniMacro mac = new UniMacro("tablename", "test.dept", "MySQL");
conn.Macros.Add(mac);
UniMacro mac2 = new UniMacro("tablename", "dept", "Oracle");
conn.Macros.Add(mac2);
//Define query that uses the macro
UniCommand cmd = new UniCommand("SELECT Count(*) FROM {tablename}");
cmd.Connection = conn;
conn.Open();
try {
  //Run the query and make sure it is executed correctly
  string response = cmd.ExecuteScalar().ToString();
  MessageBox.Show(response);
}
finally {        
  conn.Close();
}
Dim conn As New UniConnection()
'Connecting to Oracle server through dotConnect for Oracle
conn.ConnectionString = "provider=Oracle;Password=tiger;User Id=Scott;Host=ora920;"
'Define macro for table dept
Dim mac As New UniMacro("tablename", "test.dept", "MySQL")
conn.Macros.Add(mac)
Dim mac2 As New UniMacro("tablename", "dept", "Oracle")
conn.Macros.Add(mac2)
'Define query that uses the macro
Dim cmd As New UniCommand("SELECT Count(*) FROM {tablename}")
cmd.Connection = conn
conn.Open()
Try
  'Run the query and make sure it is executed correctly
  Dim response As String = cmd.ExecuteScalar().ToString()
  MessageBox.Show(response)
Finally
  conn.Close()
End Try

As you see, it is very easy to control SQL statements transformation. Now let's take a look at another example that demonstrates a whole pack of important features:

UniConnection conn = new UniConnection();
//Connecting to MySQL server through dotConnect for MySQL
conn.ConnectionString = "provider=MySQL;Password=root;User Id=root;Host=localhost;Port=3307;";
//Define table name for all databases (blank condition)
//Note the single-line macro creation in the collection
conn.Macros.Add("tablename", "emp");
//For MySQL, prepend database name
conn.Macros.Add("tablename", "test.emp", "MySQL");
//Limit records count where it is easy (MySQL and PostgreSQL)
conn.Macros.Add("limit", "LIMIT 0,5", "mysql");
conn.Macros.Add("limit", "LIMIT 5 OFFSET 0", "PostgreSQL");
//Define default FROM clause
conn.Macros.Add("from", "FROM {tablename}");
//If the limit macro is defined, add extra clause
conn.Macros.Add("from", "FROM {tablename} {limit}", "limit");
//Define query that uses the macro
UniCommand cmd = new UniCommand("SELECT EName, Job, Sal {from}");
cmd.Connection = conn;
conn.Open();
try {
  UniDataReader dataReader = cmd.ExecuteReader();
  ...
  dataReader.Close();
}
finally {
  conn.Close();
}
Dim conn As New UniConnection()
'Connecting to MySQL server through dotConnect for MySQL
conn.ConnectionString = "provider=MySQL;Password=root;User Id=root;Host=localhost;Port=3307;"
'Define table name for all databases (blank condition)
'Note the single-line macro creation in the collection
conn.Macros.Add("tablename", "emp")
'For MySQL, prepend database name
conn.Macros.Add("tablename", "test.emp", "MySQL")
'Limit records count where it is easy (MySQL and PostgreSQL)
conn.Macros.Add("limit", "LIMIT 0,5", "mysql")
conn.Macros.Add("limit", "LIMIT 5 OFFSET 0", "PostgreSQL")
'Define default FROM clause
conn.Macros.Add("from", "FROM {tablename}")
'If the limit macro is defined, add extra clause
conn.Macros.Add("from", "FROM {tablename} {limit}", "limit")
'Define query that uses the macro
Dim cmd As New UniCommand("SELECT EName, Job, Sal {from}")
cmd.Connection = conn
conn.Open()
Try
  'Run the query and make sure it is executed correctly
  Dim response As String = cmd.ExecuteScalar().ToString()
  MessageBox.Show(response)
Finally
  conn.Close()
  Dim dataReader As UniDataReader = cmd.ExecuteReader()
  ...
  dataReader.Close()
End Try

Supposed that in this sample connection is made to MySQL server, the executed statement would be

  SELECT EName, Job, Sal FROM emp LIMIT 0,5

Note: you can use DBMonitor application to see what your query turns into on execution.

A step-by step analysis of the sample reveals following important notes:

  1. If a macro has blank condition, it is always evaluated.
  2. Macro with enabled condition overrides macro with blank condition.
  3. Conditions are case-insensitive.
  4. You can use your own macros as conditions.
  5. You can use macros as part of the value of other macros.

dotConnect Universal has set of useful predefined macros that help you write universal statements. Please refer to Macros Reference for more information.

Literals and Identifiers

dotConnect Universal provides universal syntax for dates, timestamps, intervals LIKE expressions and quoted identifiers. Its usage is similar to usage of macros. Note that this functionality is not available for OLE DB, ODBC, and DB2 data providers.

Date and time constants

In date/time constants parts of date are separated with hyphen, time parts are separated with colon, and space is expected between the two parts. The following table illustrates date/time format:

Literal type Format Example
date yyyy-mm-dd {date '2006-12-31'}
time hh:mm:ss {time '23:59:59'}
timestamp yyyy-mm-dd hh:mm:ss {timestamp '2006-12-31 23:59:59'}

The following SQL statement:

  SELECT * FROM emp WHERE HIREDATE>{date '1982-01-15'}

in MySQL evaluates to

  SELECT * FROM emp WHERE HIREDATE>CAST('1982-01-15' AS DATETIME)

and in Oracle it turns to

  SELECT * FROM emp WHERE HIREDATE>TO_DATE('1982-01-15', 'YYYY-MM-DD')

Intervals

Date/time intervals are formatted similar to date/time constants. The following examples demonstrate how to add interval to or subtract from a date:

  SELECT HIREDATE+{interval '4' DAY} FROM emp;
  SELECT HIREDATE+{interval '4 10:20:30' DAY TO SECOND} FROM emp;
  SELECT HIREDATE-{interval '1-2' YEAR TO MONTH} FROM emp;

The first statement adds 4 days to selected field; the second statement adds 4 days, 10 hours, 20 minutes and 30 seconds; the last statement subtracts 14 months from the field. Note that you cannot combine MONTH or YEAR with any other qualifier in the same interval. Use two intervals instead, or express it in smaller units.

Universal quoting of identifiers

All database servers support quoting for identifiers that contain special symbols like spaces or dots. dotConnect Universal allows to wrap identifiers universally so that quotation is appropriate for every database server. Use the following syntax:

"identifier"

For example, expression "table1"."field1" turns into "table1"."field1" in Oracle and PostgreSQL, into [table1].[field1] in MS SQL Server, and into `table1`.`field1` in MySQL server. Do not confuse with single quotes, which are intended to wrap string constants.

LIKE Escape symbol

dotConnect Universal allows to perform database-independent escaping in LIKE expressions. To do this, add the following declaration after LIKE expression: {escape 'esc_symbol'} where esc_symbol is the symbol that precedes the special character. For example the following query

SELECT * FROM emp WHERE EName LIKE 'Mike\_S%' {escape '\'}

orders server to return all rows that have EName field beginning with Mike_S constant. Here the underscore symbol is not treated as "any character", while the last symbol is interpreted as wildcard.

Comments

Comments are inserted in UniSQL with two hyphens (comments out the text till the end of current line). For multiline comment, wrap it into /*...*/ sequences. Example:

--This is a single-line comment

/*This one
  spans over
  several lines*/

Conditional Execution (IF)

For the purpose of extra flexibility UniSQL supports conditional inclusion of SQL code into resulting statements. This is as simple as that:

{if my_macro} STATEMENT_1 {else} STATEMENT_2 {endif}

If macro my_macro is defined, the STATEMENT_1 is returned, otherwise STATEMENT_2 is the result of the expression. For instance:

{if Oracle} 
SELECT * FROM dept
{else} 
SELECT * FROM test.dept
{endif}

The {else} clause can be omitted. Here is a bit more sophisticated example:

SELECT {if Oracle}RowId, {endif} DeptNo, DName FROM dept

Note that you can use nested {if...} constructs to continue branching.

Outer Joins

In some old versions of Oracle server SQL syntax for OUTER JOINs differs from common standard. The problem can be solved using conditional execution, but UniSQL allows to do it even easier with {oj ...} construct:

{oj Outer_Join_Expression}

In other words, if you wrap the OUTER JOIN clause in {oj ... }, and current database server is Oracle (version earlier than 9), the clause is translated to appropriate syntax. For all other servers the OUTER JOIN clause is left as is. The following example demonstrates the rule:

SELECT 
  dept.DName, emp.EName, emp.Job
FROM
  {oj dept 
LEFT OUTER JOIN 
  emp 
ON
  dept.DeptNo = emp.DeptNo}

In Oracle, this query can be transformed to the following one:

SELECT 
  dept.DName, emp.EName, emp.Job
FROM
  dept, emp
WHERE
  dept.DeptNo = emp.DeptNo (+) 

In MySQL, the query is not much modified:

SELECT 
  dept.DName, emp.EName, emp.Job
FROM
  dept 
LEFT OUTER JOIN 
  emp 
ON
  dept.DeptNo = emp.DeptNo

Note that for OLE DB and ODBC the transformation does not take place, even if the connected server is Oracle 8.x.

Functions

dotConnect Universal introduces standard for calling common SQL functions. This is set of function names with fixed meaning. In run time the function is transformed either to corresponding native function, or to equivalent expression (for example, several functions). The construct syntax is

{fn Function_Name(parameter1 [,parameter2 ... ])}

For example, the following fragment

SELECT {fn TRIM(EName)} FROM emp

evaluates to

SELECT TRIM(EName) FROM emp

in MySQL, because there is the counterpart in the DBMS. But in MS SQL Server there is no single corresponding function, so the expression evaluates to

SELECT LTRIM(RTRIM(EName)) FROM emp

The following table lists unified functions and describes them briefly.

Function name Description
System routines
USER Returns current user name.
String routines
CHAR_LENGTH(string_exp) Returns length of string expression in characters.
LOCATE(string_exp1, string_exp2) Finds first occurrence of substring string_exp1 in string expression string_exp2.
SUBSTRING(string_exp, start, length) Returns substring from specified string string_exp.
CONCAT(string_exp1, string_exp2) Concatenates several string expressions.
CHAR(code) Converts integer values into characters.
TRIM(string_exp) Removes leading and trailing spaces from a string.
Number routines
TRUNCATE(numeric_exp, integer_exp) Returns numeric_exp truncated to integer_exp places right of the decimal point.
CEILING(numeric_exp) Returns the smallest integer value not less than numeric_exp.
Date and time routines
CURRENT_DATE Returns date part of current timestamp, that is, year, month and day.
YEAR(date_exp) Extracts year part of a timestamp.
MONTH(date_exp) Extracts month part of a timestamp.
DAY(date_exp) Extracts day part of a timestamp.
DATEADD(datepart, number, date) Returns a new datetime value based on adding an interval to the specified date. The interval is formed as number of datepart units. The following example adds two years to HireDate field:
SELECT {fn DATEADD(year,2,HireDate)} FROM emp
DATEDIFF (datepart, startdate, enddate) Returns the number of date and time boundaries crossed between two specified dates.
Conversion routines
TODATE(string_exp) Converts value to date format.
TOCHAR(any_type_exp) Converts value to string format.
TONUMBER(string_exp) Converts value to number format.

Note that function mapping is not available for OLE DB, ODBC, and DB2 data providers.

Macros Reference

The following table enumerates names of predefined macros that are enabled depending on DBMS server connected and provider used.

DBMS Macro name Data Provider Macro name
Oracle Oracle Oracle Data Provider for .NET OracleClient
dotConnect for Oracle Data Provider OraDirect
MySQL MySQL dotConnect for MySQL Data Provider MyDirect
PostgreSQL PostgreSQL dotConnect for PostgreSQL Data Provider PostgreSQLDirect
SQLite SQLite dotConnect for SQLite Data Provider SQLiteDirect
MS SQL Server SQL Server .NET Framework Data Provider for SQL Server SQLClient
any   .NET Framework Data Provider for OLE DB OLEDB
any   .NET Framework Data Provider for ODBC ODBC

There are also predefined macros that help to solve most common differences in SQL syntax. The following table enumerates them and gives translation for some databases.

Macro name Remarks Oracle MySQL PostgreSQL SQLite MS SQL
VARCHAR Evaluates to database type that represents string values. Used mainly in CAST expressions. VARCHAR2 VARCHAR VARCHAR TEXT VARCHAR
DOUBLE Evaluates to database type that represents floating point values. Used mainly in CAST expressions. NUMBER DOUBLE DOUBLE PRECISION REAL FLOAT(53)
DATETIME Evaluates to database type that represents date and time values. Used mainly in CAST expressions. DATETIME DATETIME TIMESTAMP TEXT (ISO8601), INTEGER (Unix Time) DATETIME
AS Used to define alias names for tables in FROM clauses empty string AS AS AS AS
EXCEPT Used in compound queries to exclude a subset of data MINUS Not supported EXCEPT EXCEPT EXCEPT
(not supported in versions prior to 2005)
ServerVersion Returns version of currently connected server. Depends on database server and data provider

See Also

UniMacro Class  | UniConnection Class  | dotConnect Universal articles