FTN17: FutoIn Interface - Database Version: 1.0 Date: 2017-11-23 Copyright: 2017 FutoIn Project (http://futoin.org) Authors: Andrey Galkin
Database interface is fundamental part of almost any technology stack. The primary focus is interface for classical Relational Database Systems.
Interface is split into several levels which are combined in inheritance chain.
Fundamental difference from traditional interfaces is lack of large result set support, cursors and explicit transaction control. This is done by intention to forbid undesired database operation patterns.
The very basic level for query execution with minimal safety requirements.
Transaction execution abstraction with "single call" pattern.
The overall idea is to execute a list of statements on DB side in single transaction one-by-one. After each xfer, trivial validation is done like amount of affected rows or count of rows in result. This allows creating complex intermediate checks in native DB query. Such pattern avoids blocking on usually expensive DB connection and forces to execute transaction with no client-side delays. Also, proper release of connection to DB connection pool is ensured.
If at any step an error occurs then whole transaction is rolled back.
Note: internally, it's assumed that there is a limited number of simultaneous DB connection allowed which are managed in connection pool for performance reasons, but such details are absolutely hidden from clients.
Large result streaming through BiDirectional channel. Database metadata and ORM-like abstraction. TBD.
For cross-operation support, client side should be as much neutral as possible and try to use QueryBuilder interface which should auto-detect actual database type. QueryBuilder is responsible to consistent behavior across databases. The same applies for XferBUilder in Level 2.
Client application should be able to run raw queries depending on L1.getType() result.
QueryBuilder must enforce auto-escape of all values. Identifiers must be checked for valid [fully qualified] format to prevent possible injection attacks.
QueryBuilder must throw an error at build or prepare stage, if any of configured query parts are not used.
Conditions can be set as: 1. string - must a be a plain expression used as-is: 2. map: - "field.name" => "value" or "field.name OP" pairs - value is auto-escaped 3. array - special cases for building complex conditions as tree - Optional "AND" (default) or "OR" string in first element indicate join type - any following element is recursive of these rules: string, map or another array
Multiple conditions and/or repeated calls assune "AND" join.
Simple Example:
select('TableName')
.where('SomeField IS NULL')
.where({
'OtherField' => $val,
'AnotherField <' => $threshold,
})
Complex Example:
select('TableName')
.where([
"FieldOne IS NULL",
[
"OR",
"FieldTwo <" => 2,
"FieldThee IN" => [1, 2, 3],
],
{
"FieldFour" => $val,
}
])
For map conditions optional match operators are supported. The following standard ops are assumed:
=
- equal<>
- not equal>
- greater>=
- greater or equal<
- less<=
- less or equalIN
- in array or subquery (assumed)NOT IN
- not in array or subquery (assumed)BETWEEN
- two value tuple is assumed for inclusive range matchNOT BETWEEN
- two value tuple is assumed for inverted inclusive range matchLIKE
- LIKE matchNOT LIKE
- NOT LIKE matchNote: EXISTS
, ANY
and SOME
are not supported by design due to known performance issues in many database implementations.
As there is no single approach to retrieve last insert ID across popular database implementations, a special convention is required.
Very often, transaction requires values returning from previous queries inside the same transaction. As overall concept of this spec forbids transaction processing splitting across requests a special mechanism of placeholders for value back references is required.
There are many uniqie database-specific features which may not be implemented
in all types. A generic optional Helpers
interface is provided. Each
end user application must have full coverage unit test run to ensure that
particular database flavour is supported, if helpers are used.
{
"iface" : "futoin.db.l1",
"version" : "1.0",
"ftn3rev" : "1.7",
"imports" : [
"futoin.ping:1.0"
],
"types" : {
"Query" : {
"type" : "string",
"minlen" : 1,
"maxlen" : 10000
},
"Identifier" : {
"type" : "string",
"maxlen" : 256
},
"Row" : "array",
"Rows" : {
"type" : "array",
"elemtype" : "Row",
"maxlen" : 1000
},
"Field" : {
"type" : "string",
"maxlen" : 256
},
"Fields" : {
"type" : "array",
"elemtype" : "Field",
"desc" : "List of field named in order of related Row"
},
"Flavour" : {
"type" : "Identifier",
"desc" : "Actual actual database driver type"
},
"QueryResult" : {
"type" : "map",
"fields" : {
"rows" : "Rows",
"fields" : "Fields",
"affected" : "integer"
}
}
},
"funcs" : {
"query" : {
"params" : {
"q" : "Query"
},
"result" : "QueryResult",
"throws" : [
"InvalidQuery",
"Duplicate",
"OtherExecError",
"LimitTooHigh"
]
},
"callStored" : {
"params" : {
"name" : "Identifier",
"args" : "Row"
},
"result" : "QueryResult",
"throws" : [
"InvalidQuery",
"Duplicate",
"OtherExecError",
"LimitTooHigh",
"DeadLock"
]
},
"getFlavour" : {
"result" : "Flavour"
}
}
}
seconds
interval implementation-defined
way {
"iface" : "futoin.db.l2",
"version" : "1.0",
"ftn3rev" : "1.7",
"inherit" : "futoin.db.l1:1.0",
"types" : {
"IntOrBool" : ["integer", "boolean"],
"XferQuery" : {
"type" : "map",
"fields" : {
"q" : "Query",
"affected" : {
"type" : "IntOrBool",
"optional" : true,
"desc" : "Require changed row count: specific or > 0, if true"
},
"selected" : {
"type" : "IntOrBool",
"optional" : true,
"desc" : "Require selected row count: specific or > 0, if true"
},
"result" : {
"type" : "boolean",
"optional" : true,
"desc" : "Return result of the statement"
},
"template" : {
"type" : "boolean",
"optional" : true,
"desc" : "Process value back references"
}
}
},
"XferQueryList" : {
"type" : "array",
"elemtype" : "XferQuery",
"minlen" : 1,
"maxlen" : 100
},
"XferResult" : {
"type" : "map",
"fields" : {
"seq" : "integer",
"rows" : "Rows",
"fields" : "Fields",
"affected" : "integer"
}
},
"XferResultList" : {
"type" : "array",
"elemtype" : "XferResult",
"minlen" : 0,
"maxlen" : 100
},
"IsolationLevel" : {
"type" : "enum",
"items" : ["RU", "RC", "RR", "SRL"],
"desc" : "Refers to standard ISO isolation levels"
}
},
"funcs" : {
"xfer" : {
"params" : {
"ql" : "XferQueryList",
"isol" : "IsolationLevel"
},
"result" : "XferResultList",
"throws" : [
"InvalidQuery",
"Duplicate",
"OtherExecError",
"LimitTooHigh",
"DeadLock",
"XferCondition",
"XferBackRef"
]
}
}
}
To be defined in later versions.
=END OF SPEC=