Commit b7fd2b9a authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge branch '4720MultiDB'

# Conflicts:
#	extension/qfq/qfq/form/TypeAhead.php
parents 1eeaa96c 348149f9
...@@ -34,13 +34,6 @@ qfq.zip: ...@@ -34,13 +34,6 @@ qfq.zip:
clean: clean:
cd doc/diagram ; $(MAKE) $@ cd doc/diagram ; $(MAKE) $@
update-qfq-doc:
rsync -av --delete --exclude=_make --exclude=_static extension/Documentation/ ../qfq-doc/Documentation/
cd ../qfq-doc
git commit -a
git push
cd ../qfq
git-revision: make-dist-dir git-revision: make-dist-dir
echo $(GIT_REVISION_LONG) > $(DISTDIR)/revision.git echo $(GIT_REVISION_LONG) > $(DISTDIR)/revision.git
......
...@@ -792,8 +792,10 @@ System tables ...@@ -792,8 +792,10 @@ System tables
* Check Bug #5459 - support of system tables in different DBs not supported. * Check Bug #5459 - support of system tables in different DBs not supported.
Multi Databases .. _`multi-database`:
^^^^^^^^^^^^^^^
Multi Database
^^^^^^^^^^^^^^
Base: T3 & QFQ Base: T3 & QFQ
'''''''''''''' ''''''''''''''
...@@ -1039,20 +1041,55 @@ SQL variables ...@@ -1039,20 +1041,55 @@ SQL variables
* INSERT, UPDATE, DELETE, REPLACE, TRUNCATE * INSERT, UPDATE, DELETE, REPLACE, TRUNCATE
* SHOW, DESCRIBE, EXPLAIN, SET * SHOW, DESCRIBE, EXPLAIN, SET
* A SQL Statement might contain parameters, including additional SQL statements. Inner SQL queries will be executed first. * A SQL Statement might contain variables, including additional SQL statements. Inner SQL queries will be executed first.
* All variables will be substituted one by one from inner to outer. * All variables will be substituted one by one from inner to outer.
* The number of variables inside an input field or a SQL statement is not limited. * The number of variables inside an input field or a SQL statement is not limited.
* A resultset of a SQL statement will be imploded over all: concat all columns of a row, concat all rows - there is no glue string.
* Example:: Result: string
''''''''''''''
A result of a SQL statement will be imploded over all: concat all columns of a row, concat all rows - there is no
glue string.
Result: row
'''''''''''
A few functionalities needs more than a returned string, instead separate columns are necessary. To indicate an array
result, specify those with an '!': ::
{{!SELECT ...}}
This manual will specify the individual QFQ elements, who needs an array instead of a string. It's an error to return
a string where an array is needed and vice versa.
Database index
''''''''''''''
To access different databases in a `multi-database`_ setup, the database index can be specified after the opening curly
braces. ::
{{[1]SELECT ... }}
For using the DB_INDEX_DATA and DB_INDEX_QFQ (`config.qfq.ini`_), it's a good practice to specify the variable name
instead of the numeric index. ::
{{[{{DB_INDEX_DATA:Y}}]SELECT ...}}
If no dbIndex is given, `{{DB_INDEX_DATA:Y}}` is used.
Example
'''''''
::
{{SELECT id, name FROM Person}} {{SELECT id, name FROM Person}}
{{SELECT id, name, IF({{feUser:T0}}=0,'Yes','No') FROM Person WHERE id={{r:S}} }} {{SELECT id, name, IF({{feUser:T0}}=0,'Yes','No') FROM Person WHERE id={{r:S}} }}
{{SELECT id, city FROM Address AS adr WHERE adr.accId={{SELECT id FROM Account AS acc WHERE acc.name={{feUser:T0}} }} }} {{SELECT id, city FROM Address AS adr WHERE adr.accId={{SELECT id FROM Account AS acc WHERE acc.name={{feUser:T0}} }} }}
{{!SELECT id, name FROM Person}}
{{[2]SELECT id, name FROM Form}}
{{[{{DB_INDEX_QFQ:Y}}]SELECT id, name FROM Form}}
* Special case for `SELECT` input fields and FormElement.type=action `sqlValidate`. To deliver a result array specify an '!' before the SELECT: ::
{{!SELECT ...}}
.. _`column-variables`: .. _`column-variables`:
......
...@@ -568,7 +568,6 @@ abstract class AbstractBuildForm { ...@@ -568,7 +568,6 @@ abstract class AbstractBuildForm {
$this->store->appendToStore($fe[FE_FILL_STORE_VAR], STORE_VAR); $this->store->appendToStore($fe[FE_FILL_STORE_VAR], STORE_VAR);
} }
// ** evaluate current FormElement ** // ** evaluate current FormElement **
$formElement = $this->evaluate->parseArray($fe, $skip, $debugStack); $formElement = $this->evaluate->parseArray($fe, $skip, $debugStack);
$formElement = HelperFormElement::setLanguage($formElement, $parameterLanguageFieldName); $formElement = HelperFormElement::setLanguage($formElement, $parameterLanguageFieldName);
...@@ -1257,9 +1256,17 @@ abstract class AbstractBuildForm { ...@@ -1257,9 +1256,17 @@ abstract class AbstractBuildForm {
* @throws \qfq\UserFormException * @throws \qfq\UserFormException
*/ */
private function checkSqlAppendLimit($sql, $limit) { private function checkSqlAppendLimit($sql, $limit) {
$sqlTest = '';
$sql = trim($sql); $sql = trim($sql);
if (false === stristr(substr($sql, 0, 7), 'SELECT ')) { if ($sql[0] = '[') {
// Remove optional existing dbIndex token.
$pos = strpos($sql, ']');
$sqlTest = substr($sql, $pos + 1);
}
if (false === stristr(substr($sqlTest, 0, 7), 'SELECT ')) {
throw new UserFormException("Expect a SELECT statement in " . FE_TYPEAHEAD_SQL . " - got: " . $sql, ERROR_BROKEN_PARAMETER); throw new UserFormException("Expect a SELECT statement in " . FE_TYPEAHEAD_SQL . " - got: " . $sql, ERROR_BROKEN_PARAMETER);
} }
......
...@@ -129,7 +129,7 @@ const ERROR_USER_LOGGED_IN = 1018; ...@@ -129,7 +129,7 @@ const ERROR_USER_LOGGED_IN = 1018;
const ERROR_FORM_FORBIDDEN = 1019; const ERROR_FORM_FORBIDDEN = 1019;
const ERROR_FORM_UNKNOWN_PERMISSION_MODE = 1020; const ERROR_FORM_UNKNOWN_PERMISSION_MODE = 1020;
const ERROR_MULTI_SQL_MISSING = 1021; const ERROR_MULTI_SQL_MISSING = 1021;
const ERROR_TOKEN_MISSING = 1022;
const ERROR_RECURSION_TOO_DEEP = 1023; const ERROR_RECURSION_TOO_DEEP = 1023;
const ERROR_CHECKBOXMODE_UNKNOWN = 1024; const ERROR_CHECKBOXMODE_UNKNOWN = 1024;
const ERROR_MISSING_SQL1 = 1025; const ERROR_MISSING_SQL1 = 1025;
...@@ -796,7 +796,6 @@ const F_PARAMETER = 'parameter'; // valid for F_ and FE_ ...@@ -796,7 +796,6 @@ const F_PARAMETER = 'parameter'; // valid for F_ and FE_
// Form columns: via parameter field // Form columns: via parameter field
const F_DB_INDEX = 'dbIndex'; const F_DB_INDEX = 'dbIndex';
const F_DB_INDEX_EXTRA = 'dbIndexExtra';
const DB_INDEX_DEFAULT = "1"; const DB_INDEX_DEFAULT = "1";
const PARAM_DB_INDEX_DATA = '__dbIndexData'; // Submitted via SIP to make record locking DB aware. const PARAM_DB_INDEX_DATA = '__dbIndexData'; // Submitted via SIP to make record locking DB aware.
......
...@@ -14,6 +14,7 @@ require_once(__DIR__ . '/../qfq/store/Store.php'); ...@@ -14,6 +14,7 @@ require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/database/Database.php'); require_once(__DIR__ . '/../qfq/database/Database.php');
require_once(__DIR__ . '/helper/Support.php'); require_once(__DIR__ . '/helper/Support.php');
const EVALUATE_DB_INDEX_DEFAULT = 0;
/** /**
* Class Evaluate * Class Evaluate
* @package qfq * @package qfq
...@@ -23,11 +24,13 @@ class Evaluate { ...@@ -23,11 +24,13 @@ class Evaluate {
* @var Store * @var Store
*/ */
private $store = null; private $store = null;
/** /**
* @var Database * @var Database[]
*/ */
private $db = null; private $dbArray = array();
private $dbIndex = EVALUATE_DB_INDEX_DEFAULT;
private $startDelimiter = ''; private $startDelimiter = '';
private $startDelimiterLength = 0; private $startDelimiterLength = 0;
private $endDelimiter = ''; private $endDelimiter = '';
...@@ -46,7 +49,8 @@ class Evaluate { ...@@ -46,7 +49,8 @@ class Evaluate {
*/ */
public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') { public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') {
$this->store = $store; $this->store = $store;
$this->db = $db;
$this->dbArray[EVALUATE_DB_INDEX_DEFAULT] = $db;
$this->startDelimiter = $startDelimiter; $this->startDelimiter = $startDelimiter;
$this->startDelimiterLength = strlen($startDelimiter); $this->startDelimiterLength = strlen($startDelimiter);
$this->endDelimiter = $endDelimiter; $this->endDelimiter = $endDelimiter;
...@@ -197,6 +201,20 @@ class Evaluate { ...@@ -197,6 +201,20 @@ class Evaluate {
$sqlMode = ROW_IMPLODE_ALL; $sqlMode = ROW_IMPLODE_ALL;
$token = trim($token); $token = trim($token);
$dbIndex = $this->dbIndex;
// Check if the $token starts with '[<int>]...' - yes: open the necessary database.
if ($token[0] === '[') {
if ($token[2] !== ']') {
throw new UserFormException("Missing token ']' in '$token' on position 3", ERROR_TOKEN_MISSING);
}
$dbIndex = $token[1];
$token = trim(substr($token, 3));
if (empty($this->dbArray[$dbIndex])) {
$this->dbArray[$dbIndex] = new Database($dbIndex);
}
}
if ($token[0] === '!') { if ($token[0] === '!') {
$token = trim(substr($token, 1)); $token = trim(substr($token, 1));
...@@ -210,7 +228,7 @@ class Evaluate { ...@@ -210,7 +228,7 @@ class Evaluate {
if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) { if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {
$foundInStore = TOKEN_FOUND_IN_STORE_QUERY; $foundInStore = TOKEN_FOUND_IN_STORE_QUERY;
return $this->db->sql($token, $sqlMode); return $this->dbArray[$dbIndex]->sql($token, $sqlMode);
} }
// explode for: <key>:<store priority>:<sanitize class>:<escape>:<default> // explode for: <key>:<store priority>:<sanitize class>:<escape>:<default>
...@@ -244,7 +262,7 @@ class Evaluate { ...@@ -244,7 +262,7 @@ class Evaluate {
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN); $value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN);
break; break;
case TOKEN_ESCAPE_MYSQL: case TOKEN_ESCAPE_MYSQL:
$value = $this->db->realEscapeString($value); $value = $this->dbArray[$dbIndex]->realEscapeString($value);
break; break;
case TOKEN_ESCAPE_NONE: // do nothing case TOKEN_ESCAPE_NONE: // do nothing
break; break;
......
...@@ -22,12 +22,12 @@ class TypeAhead { ...@@ -22,12 +22,12 @@ class TypeAhead {
/** /**
* @var Database instantiated class * @var Database instantiated class
*/ */
protected $db = null; private $db = null;
/** /**
* @var array * @var array
*/ */
protected $vars = array(); private $vars = array();
/** /**
* *
...@@ -47,9 +47,6 @@ class TypeAhead { ...@@ -47,9 +47,6 @@ class TypeAhead {
} }
$session = Session::getInstance($phpUnit); $session = Session::getInstance($phpUnit);
$this->db = new Database();
} }
/** /**
...@@ -61,11 +58,22 @@ class TypeAhead { ...@@ -61,11 +58,22 @@ class TypeAhead {
public function process() { public function process() {
$arr = array(); $arr = array();
$dbIndex = DB_INDEX_DEFAULT;
$sipClass = new Sip(); $sipClass = new Sip();
$sipVars = $sipClass->getVarsFromSip($this->vars[TYPEAHEAD_API_SIP]); $sipVars = $sipClass->getVarsFromSip($this->vars[TYPEAHEAD_API_SIP]);
// Check for an optional given dbIndex: '[<int>]SELECT ...'
$sql = $sipVars[FE_TYPEAHEAD_SQL];
if ($sql[0] === '[') {
$pos = strpos($sql, ']');
$dbIndex = substr($sql, 1, $pos - 1);
$sipVars[FE_TYPEAHEAD_SQL] = substr($sql, $pos + 1);
}
$this->db = new Database($dbIndex);
if (isset($sipVars[FE_TYPEAHEAD_SQL])) { if (isset($sipVars[FE_TYPEAHEAD_SQL])) {
if($this->vars[TYPEAHEAD_API_PREFETCH] == '') { if($this->vars[TYPEAHEAD_API_PREFETCH] == '') {
$arr = $this->typeAheadSql($sipVars, $this->vars[TYPEAHEAD_API_QUERY]); $arr = $this->typeAheadSql($sipVars, $this->vars[TYPEAHEAD_API_QUERY]);
......
...@@ -211,7 +211,7 @@ VALUES ...@@ -211,7 +211,7 @@ VALUES
1, '', '', '', 'specialchar', 'no', '[a-zA-Z0-9._+-]+'), 1, '', '', '', 'specialchar', 'no', '[a-zA-Z0-9._+-]+'),
(1, 'title', 'Title', 'show', 'text', 'all', 'native', 130, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-title">Info</a>', '', '', '', '', 1, '', '', '', 'none', 'no', ''), (1, 'title', 'Title', 'show', 'text', 'all', 'native', 130, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-title">Info</a>', '', '', '', '', 1, '', '', '', 'none', 'no', ''),
(1, 'noteInternal', 'Note', 'show', 'text', 'all', 'native', 140, '40,3', 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-note">Info</a>', '', '', '', '', 1, '', '', '', 'specialchar', 'no', ''), (1, 'noteInternal', 'Note', 'show', 'text', 'all', 'native', 140, '40,3', 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-note">Info</a>', '', '', '', '', 1, '', '', '', 'specialchar', 'no', ''),
(1, 'tableName', 'Table', 'required', 'select', 'all', 'native', 150, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-tablename">Info</a>', '', '', '{{!SHOW tables}}', (1, 'tableName', 'Table', 'required', 'select', 'all', 'native', 150, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#form-tablename">Info</a>', '', '', '{{[{{DB_INDEX_DATA:Y}}]!SHOW tables}}',
'emptyItemAtStart', 1, '', '', '', 'specialchar', 'no', ''), 'emptyItemAtStart', 1, '', '', '', 'specialchar', 'no', ''),
(1, 'parameterLanguageA', 'Language: {{FORM_LANGUAGE_A_LABEL:YE}}', 'show', 'text', 'all', 'native', 160, '60,2', 0, (1, 'parameterLanguageA', 'Language: {{FORM_LANGUAGE_A_LABEL:YE}}', 'show', 'text', 'all', 'native', 160, '60,2', 0,
'<a href="{{DOCUMENTATION_QFQ:Y}}#multi-language-form">Info</a>', '', '', '', '', 1, '', '{{SELECT IF("{{FORM_LANGUAGE_A_ID:YE}}"="","hidden","show" ) }}', '', 'none', 'no', ''), '<a href="{{DOCUMENTATION_QFQ:Y}}#multi-language-form">Info</a>', '', '', '', '', 1, '', '{{SELECT IF("{{FORM_LANGUAGE_A_ID:YE}}"="","hidden","show" ) }}', '', 'none', 'no', ''),
...@@ -295,7 +295,7 @@ VALUES ...@@ -295,7 +295,7 @@ VALUES
(2, 'dynamicUpdate', 'Dynamic Update', 'show', 'checkbox', 'all', 'native', 135, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#dynamic-update">Info</a>', (2, 'dynamicUpdate', 'Dynamic Update', 'show', 'checkbox', 'all', 'native', 135, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#dynamic-update">Info</a>',
'', '', '', '', 100, '', 'no', '', '', '', '', '', 'specialchar'), '', '', '', '', 100, '', 'no', '', '', '', '', '', 'specialchar'),
(2, 'name', 'Name', 'show', 'text', 'all', 'native', 140, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', '', '', '', (2, 'name', 'Name', 'show', 'text', 'all', 'native', 140, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', '', '', '',
'typeAheadSql = SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = "{{DB_1_NAME:Y}}" AND table_name = "{{SELECT f.tableName FROM Form AS f WHERE f.id={{formId:S0}}}}" AND COLUMN_NAME LIKE ? ORDER BY COLUMN_NAME\ntypeAheadMinLength = 1\ntypeAheadLimit = 100\ntypeAheadPedantic = 0\n', 'typeAheadSql = [{{DB_INDEX_DATA:Y}}]SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = "{{DB_1_NAME:Y}}" AND table_name = "{{SELECT f.tableName FROM Form AS f WHERE f.id={{formId:S0}}}}" AND COLUMN_NAME LIKE ? ORDER BY COLUMN_NAME\ntypeAheadMinLength = 1\ntypeAheadLimit = 100\ntypeAheadPedantic = 0\n',
100, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', 'no', '', '', '', '', '', 'specialchar'), 100, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', 'no', '', '', '', '', '', 'specialchar'),
(2, 'label', 'Label', 'show', 'text', 'all', 'native', 150, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', '', '', '', '', 100, '', 'no', '', '', '', '', '', 'none'), (2, 'label', 'Label', 'show', 'text', 'all', 'native', 150, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', '', '', '', '', 100, '', 'no', '', '', '', '', '', 'none'),
(2, 'mode', 'Mode', 'show', 'radio', 'all', 'native', 160, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', '', '', '', 'buttonClass=btn-default', 100, '', 'no', '', '', '', '', '', 'specialchar'), (2, 'mode', 'Mode', 'show', 'radio', 'all', 'native', 160, 0, 0, '<a href="{{DOCUMENTATION_QFQ:Y}}#class-native">Info</a>', '', '', '', 'buttonClass=btn-default', 100, '', 'no', '', '', '', '', '', 'specialchar'),
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment