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

Feature #4720 - Implemented {{[<int]SELECT ...}} to switch between different databases;

parent bd6efa0f
......@@ -1009,20 +1009,42 @@ SQL variables
* INSERT, UPDATE, DELETE, REPLACE, TRUNCATE
* 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.
* 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
''''''''''''''
Multi Database setups needs access to different databases.
Example
'''''''
::
{{SELECT id, name FROM Person}}
{{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}} }} }}
* Special case for `SELECT` input fields and FormElement.type=action `sqlValidate`. To deliver a result array specify an '!' before the SELECT: ::
{{!SELECT ...}}
.. _`column-variables`:
......
......@@ -1271,9 +1271,17 @@ abstract class AbstractBuildForm {
* @throws \qfq\UserFormException
*/
private function checkSqlAppendLimit($sql, $limit) {
$sqlTest = '';
$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);
}
......
......@@ -129,7 +129,7 @@ const ERROR_USER_LOGGED_IN = 1018;
const ERROR_FORM_FORBIDDEN = 1019;
const ERROR_FORM_UNKNOWN_PERMISSION_MODE = 1020;
const ERROR_MULTI_SQL_MISSING = 1021;
const ERROR_TOKEN_MISSING = 1022;
const ERROR_RECURSION_TOO_DEEP = 1023;
const ERROR_CHECKBOXMODE_UNKNOWN = 1024;
const ERROR_MISSING_SQL1 = 1025;
......
......@@ -14,6 +14,7 @@ require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/database/Database.php');
require_once(__DIR__ . '/helper/Support.php');
const EVALUATE_DB_INDEX_DEFAULT = 0;
/**
* Class Evaluate
* @package qfq
......@@ -23,11 +24,13 @@ class Evaluate {
* @var Store
*/
private $store = null;
/**
* @var Database
* @var Database[]
*/
private $db = null;
private $dbArray = array();
private $dbIndex = EVALUATE_DB_INDEX_DEFAULT;
private $startDelimiter = '';
private $startDelimiterLength = 0;
private $endDelimiter = '';
......@@ -46,7 +49,8 @@ class Evaluate {
*/
public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') {
$this->store = $store;
$this->db = $db;
$this->dbArray[EVALUATE_DB_INDEX_DEFAULT] = $db;
$this->startDelimiter = $startDelimiter;
$this->startDelimiterLength = strlen($startDelimiter);
$this->endDelimiter = $endDelimiter;
......@@ -197,6 +201,20 @@ class Evaluate {
$sqlMode = ROW_IMPLODE_ALL;
$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] === '!') {
$token = trim(substr($token, 1));
......@@ -210,7 +228,7 @@ class Evaluate {
if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {
$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>
......@@ -244,7 +262,7 @@ class Evaluate {
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN);
break;
case TOKEN_ESCAPE_MYSQL:
$value = $this->db->realEscapeString($value);
$value = $this->dbArray[$dbIndex]->realEscapeString($value);
break;
case TOKEN_ESCAPE_NONE: // do nothing
break;
......
......@@ -22,12 +22,12 @@ class TypeAhead {
/**
* @var Database instantiated class
*/
protected $db = null;
private $db = null;
/**
* @var array
*/
protected $vars = array();
private $vars = array();
/**
*
......@@ -47,9 +47,6 @@ class TypeAhead {
}
$session = Session::getInstance($phpUnit);
$this->db = new Database();
}
/**
......@@ -61,12 +58,22 @@ class TypeAhead {
public function process() {
$arr = array();
$values = array();
$dbIndex = DB_INDEX_DEFAULT;
$sipClass = new 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])) {
$arr = $this->typeAheadSql($sipVars, $this->vars[TYPEAHEAD_API_QUERY]);
} elseif (isset($sipVars[FE_LDAP_SERVER])) {
......
......@@ -211,7 +211,7 @@ VALUES
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, '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', ''),
(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', ''),
......@@ -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>',
'', '', '', '', 100, '', 'no', '', '', '', '', '', 'specialchar'),
(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'),
(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'),
......
Markdown is supported
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