Database Level 1
Protocol-level concept is very simple. Most complex logic is implemented on client side. Therefore, client can be updated to newer versions without server side changes. Also, there is minimal processing load on server side.
Assumptions
Environment neutrality requirements
- Date/time must be forced to UTC in all database operations
- Multiple statement execution must be forbidden in single query() call
- Unicode charset is assumed
- Date/time conversion to/from native objects is implementation defined, but ISO-like strings are preferred.
- String representation should be used, unless there is a native runtime type which can represent DB type without doubt and side-effects.
Insert ID concept
As there is no single approach to retrieve last insert ID across popular database implementations, a special convention is required.
- For database implementations which do not support select-like query on insert operation, last insert ID must be unconditionally returned as ‘$id’ field on first result row.
- For other databases, user is responsible for adding RETURNING, OUTPUT or similar implementation-specific clause.
- For neutral QueryBuilder as special method getInsertID(field) is to be used which always ensures ‘$id’ field in response of successful insert operation.
Protocol Level API
query(as, q)
Executes raw query as is. Result has two variables:
rows
is array of field value arrays.fields
- is array of field names.affected
- count of affected rows.
Native associateResult()
function can be used to get array
of field=>value maps.
It is discouraged to use this function directly.
QueryBuilder
should be used instead.
Note: the result is packed this way for network size efficiency.
callStored( as, name, args)
Call stored procedure. Result is the same as in query()
.
Note: not all database engines support this feature.
getFlavour( as )
Get database engine type
Client-side native API
Below is list of additional native interface functions. This level also provides QueryBuilder and Helpers described separately.
-
QueryBuilder queryBuilder(type, entity)
type
- DELETE, INSERT, SELECT, UPDATE-
entity
-- table or view name
- QueryBuilder object to use as sub-query
- tuple of [entity, alias]
- null - special case without SQL “FROM”
alias
- alias to use for referencing
-
QueryBuilder delete(entity)
- calls
queryBuilder('DELETE', entity)
- calls
-
QueryBuilder insert(entity)
- calls
queryBuilder('INSERT', entity)
- calls
-
QueryBuilder select(entity)
- calls
queryBuilder('SELECT', entity)
- calls
-
QueryBuilder update(entity)
- calls
queryBuilder('UPDATE', entity)
- calls
-
void paramQuery(as, String q, Map params={})
- substitute
:name
placeholders inq
with values fromparams
. - then call normal raw
query()
.
- substitute
-
Array associateResult(as_result)
- process efficiently packed result to get array of associative Map
-
Prepared getPrepared(unique_key, callback)
- A feature for easy re-using prepared statements
- checks if prepared statement has been already cached with unique key and retuns one if found
- otherwise, calls callback, stores result and returns it
-
Helpers helpers()
- get associated
Helpers
instance
- get associated
Prepared satements
Unlike some database engines which allow server-side statement preparation, this concept assumes preparation of client-side statement in native database engine format.
Typical benefit is removal of repeated complex Query or Xfer builder logic processing on each statement. Instead raw parametrized query is used.
Below is interface of Prepared
statement:
-
class Prepared
:-
void execute(AsyncSteps as, params=null)
params
- name => value pairs for substitution- executes already built query with optional parameters
-
void executeAssoc(AsyncSteps as, params=null)
- the same as
execute()
, but return associated result
- the same as
-
Parametrized queries
Usually, such queries are created by builders, but it’s also possible to use
custom raw queries with :name
placeholders for named parameters to be substituted
at execution time. Actual substitution happens on client side.
Examples
1. Raw query with result association
const db = ccm.db();
db.query('SELECT * FROM users');
as.add( (as, res) => {
res = db.associateResult(res);
for ( let r of res ) {
const { id, first, last } = r;
// ...
}
} );
2. Simple use of QueryBuilder
// executeAssoc() calls associateResult() internally
ccm.db().select( 'users' ).executeAssoc( as );
as.add( (as, rows) => {
for ( let r of rows ) {
const { id, first, last } = r;
// ...
}
} );
3. Use of prepared statement
The benefit comes when very complex QueryBuilder or XferBuilder logic is used.
// Use as unique key defined once
const SYM_PREPARED = Symbol('SYM_PREPARED');
const db = ccm.db();
const pq = db.getPrapared( SYM_PREPARED, (db) => {
return db.select( 'users' ).prepare();
} );
pq.executeAssoc( as );
as.add( (as, rows) => {
for ( let r of rows ) {
const { id, first, last } = r;
// ...
}
} );