Merge branch 'ztflow-qixinzhi-main#qixinzhi' of zentao/zentaopms (#586)
This commit is contained in:
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -756,7 +756,7 @@ $config->db->sqliteBlacklist = array('sqlite_queue', 'cron');
|
||||
$config->hasDropmenuApps = array('program', 'project', 'product', 'execution', 'qa', 'admin', 'bi', 'feedback', 'demandpool');
|
||||
$config->hasBranchMenuModules = array('product', 'story', 'release', 'bug', 'testcase', 'testtask', 'branch', 'tree');
|
||||
$config->excludeBranchMenu = array('product-dashboard', 'product-view');
|
||||
$config->excludeDropmenuList = array('program-browse', 'program-productview', 'program-kanban', 'product-all', 'product-index', 'product-kanban', 'project-kanban', 'execution-all', 'execution-executionkanban', 'project-browse', 'project-batchedit', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create', 'metric-preview', 'metric-browse', 'metric-view', 'metriclib-browse', 'qa-index', 'caselib-create', 'feedback-batchedit', 'feedback-batchclose', 'dimension-browse');
|
||||
$config->excludeDropmenuList = array('program-browse', 'program-productview', 'program-kanban', 'product-all', 'product-index', 'product-kanban', 'project-kanban', 'execution-all', 'execution-executionkanban', 'project-browse', 'project-batchedit', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create', 'execution-batchedit', 'metric-preview', 'metric-browse', 'metric-view', 'metriclib-browse', 'qa-index', 'caselib-create', 'feedback-batchedit', 'feedback-batchclose', 'dimension-browse', 'dataview-browse');
|
||||
$config->hasSwitcherModules = array('design');
|
||||
$config->hasSwitcherMethods = array('project-bug', 'project-testcase', 'execution-bug', 'execution-testcase', 'testtask-cases', 'testtask-view', 'testtask-report', 'testtask-groupcase', 'testtask-linkcase');
|
||||
$config->excludeSwitcherList = array();
|
||||
|
||||
@@ -53,7 +53,7 @@ UPDATE `zt_config` SET `module` = 'product', `section` = 'browse' WHERE `module`
|
||||
UPDATE `zt_config` SET `module` = 'project', `section` = 'bug' WHERE `module` = 'datatable' AND `section` = 'projectBug' AND `key` = 'showModule';
|
||||
UPDATE `zt_config` SET `module` = 'testcase', `section` = 'browse' WHERE `module` = 'datatable' AND `section` = 'testcaseBrowse' AND `key` = 'showModule';
|
||||
|
||||
INSERT IGNORE INTO `zt_config` (`owner`, `module`, `key`, `value`) VALUES ('system', 'execution', 'defaultWorkhours', '7');
|
||||
REPLACE INTO `zt_config` (`owner`, `module`, `key`, `value`) VALUES ('system', 'execution', 'defaultWorkhours', '7');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `zt_session` (
|
||||
`id` varchar(32) NOT NULL,
|
||||
|
||||
@@ -41,3 +41,12 @@ UPDATE `zt_kanbancard` SET `color` = '#ff9f46' WHERE `color` = '#2a5f29';
|
||||
INSERT INTO `zt_cron`(`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES ('0', '*/1', '*', '*', '*', 'moduleName=metric&methodName=updateDashboardMetricLib', '计算仪表盘数据', 'zentao', 1, 'normal', NUll);
|
||||
|
||||
UPDATE `zt_todo` SET `type` = 'custom' WHERE `type` = 'cycle' AND `cycle` = 0;
|
||||
|
||||
INSERT INTO `zt_metric` (`purpose`, `scope`, `object`, `stage`, `type`, `name`, `code`, `alias`, `unit`, `desc`, `definition`, `when`, `createdBy`, `createdDate`, `builtin`, `deleted`, `dateType`) VALUES
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的每周新增反馈数', 'count_of_weekly_created_feedback_in_product', '新增反馈数', 'count', '按产品统计的每周新增反馈数是指在一个周内收集到的用户反馈的数量。这个度量项可以帮助团队了解用户对产品的发展趋势和需求变化,并进行产品策略的调整和优化', '产品中创建时间为某个周的反馈的个数求和\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'week'),
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的处理中的反馈数', 'count_of_doing_feedback_in_product', '处理中反馈数', 'count', '按产品统计的处理中的反馈数表示产品中状态为处理中的反馈数量之和。该数值越大,说明团队并行处理的反馈越多,可以帮助团队了解当前的工作负载情况', '产品中所有反馈个数求和\r\n状态为处理中\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'nodate'),
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的已处理的反馈数', 'count_of_done_feedback_in_product', '已处理反馈数', 'count', '按产品统计的已处理的反馈数表示产品中状态为已处理的反馈数量之和。该数值越大,说明团队成员处理的反馈越多,有利于提高用户满意度', '产品中所有反馈个数求和\r\n状态为已处理\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'nodate'),
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的待完善的反馈数', 'count_of_clarify_feedback_in_product', '待完善反馈数', 'count', '按产品统计的待完善的反馈数表示产品中状态为待完善的反馈数量之和。该数值越大,说明有较多的反馈信息不清晰或比较复杂。需要反馈者更多的澄清和解释', '产品中所有反馈个数求和\r\n状态为待完善\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'nodate'),
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的待处理的反馈数', 'count_of_wait_feedback_in_product', '待处理反馈数', 'count', '按产品统计的待处理的反馈数表示产品中状态为待处理的反馈数量之和。该度量项可能暗示产品团队的反馈处理效率,待处理反馈数越多,可能会导致客户满意度降低', '产品中所有反馈个数求和\r\n状态为待处理\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'nodate'),
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的追问中的反馈数', 'count_of_asked_feedback_in_product', '追问中反馈数', 'count', '按产品统计的追问中的反馈数表示产品中状态为追问中的反馈数量之和。该度量项可能暗示着反馈的复杂性或对处理方案的疑惑,追问中的反馈数量越多,可能意味着团队需要更多时间和资源来回复并解决这些问题', '产品中所有反馈个数求和\r\n状态为追问中\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'nodate'),
|
||||
('scale', 'product', 'feedback', 'released', 'php', '按产品统计的未关闭的反馈数', 'count_of_unclosed_feedback_in_product', '未关闭反馈数', 'count', '按产品统计的未关闭的反馈数表示产品中状态为未关闭的反馈数量之和。这个度量项可以一定程度反映产品团队响应用户反馈的效率和及时处理用户问题的能力', '产品中所有反馈个数求和\r\n过滤状态为已关闭的反馈\r\n过滤已删除的反馈\r\n过滤已删除的产品', 'realtime', 'system', '2024-05-07 08:00:00', '1', '0', 'nodate');
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `zt_pivot` ADD `driver` enum('mysql', 'duckdb') not NULL default 'mysql' AFTER `code`;
|
||||
ALTER TABLE `zt_chart` ADD `driver` enum('mysql', 'duckdb') not NULL default 'mysql' AFTER `code`;
|
||||
+13
-2
@@ -491,6 +491,7 @@ CREATE TABLE IF NOT EXISTS `zt_chart` (
|
||||
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`code` varchar(255) NOT NULL DEFAULT '',
|
||||
`driver` enum('mysql', 'duckdb') not NULL default 'duckdb';
|
||||
`dimension` mediumint(8) unsigned NOT NULL DEFAULT '0',
|
||||
`type` varchar(30) NOT NULL DEFAULT '',
|
||||
`group` varchar(255) NOT NULL DEFAULT '',
|
||||
@@ -15545,6 +15546,7 @@ CREATE TABLE IF NOT EXISTS `zt_pivot` (
|
||||
`dimension` mediumint(8) unsigned NOT NULL DEFAULT 0,
|
||||
`group` varchar(255) NOT NULL DEFAULT '',
|
||||
`code` varchar(255) NOT NULL DEFAULT '',
|
||||
`driver` enum('mysql', 'duckdb') not NULL default 'duckdb';
|
||||
`name` text NULL,
|
||||
`desc` text NULL,
|
||||
`sql` mediumtext NULL,
|
||||
@@ -16655,5 +16657,14 @@ CREATE INDEX `date` ON zt_metriclib (date);
|
||||
INSERT INTO `zt_cron`(`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES ('1', '1', '*', '*', '*', 'moduleName=metric&methodName=updateMetricLib', '计算度量数据', 'zentao', 1, 'normal', NUll);
|
||||
INSERT INTO `zt_cron`(`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES ('0', '*/1', '*', '*', '*', 'moduleName=metric&methodName=updateDashboardMetricLib', '计算仪表盘数据', 'zentao', 1, 'normal', NUll);
|
||||
|
||||
INSERT INTO `zt_config` (`vision`, `owner`, `module`, `section`, `key`, `value`) VALUES ('', 'system', 'common', '', 'closedFeatures', 'otherOA');
|
||||
INSERT INTO `zt_config` (`vision`, `owner`, `module`, `section`, `key`, `value`) VALUES ('', 'system', 'common', 'global', 'installedDate', CURDATE());
|
||||
INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`) VALUES ('*/10', '*', '*', '*', '*', 'moduleName=bi&methodName=syncParquetFile', '生成parquet文件', 'zentao', 1, 'normal');
|
||||
|
||||
UPDATE `zt_chart` SET `createdBy` = 'system' where `createdBy` = 'admin';
|
||||
UPDATE `zt_pivot` SET `createdBy` = 'system' where `createdBy` = 'admin';
|
||||
UPDATE `zt_pivot` SET `createdBy` = 'system' where `createdBy` = 'admin';
|
||||
UPDATE `zt_chart` SET `editedBy` = 'system' where `editedBy` = 'admin';
|
||||
UPDATE `zt_pivot` SET `editedBy` = 'system' where `editedBy` = 'admin';
|
||||
UPDATE `zt_pivot` SET `editedBy` = 'system' where `editedBy` = 'admin';
|
||||
|
||||
INSERT INTO `zt_config` ( `vision`, `owner`, `module`, `section`, `key`, `value` ) VALUES ('', 'system', 'common', '', 'closedFeatures', 'otherOA');
|
||||
INSERT INTO `zt_config`(`vision`, `owner`, `module`, `section`, `key`, `value`) VALUES ('', 'system', 'common', 'global', 'installedDate', CURDATE());
|
||||
|
||||
@@ -465,6 +465,28 @@ class router extends baseRouter
|
||||
return $this->loadModuleConfig($moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sql driver instance.
|
||||
*
|
||||
* @param string $driverName mysql|duckdb
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function loadDriver($driverName = 'mysql')
|
||||
{
|
||||
if($driverName == 'mysql')
|
||||
{
|
||||
return $this->dbh;
|
||||
}
|
||||
elseif($driverName == 'duckdb')
|
||||
{
|
||||
$this->loadClass('duckdb');
|
||||
$duckdb = new duckdb();
|
||||
|
||||
return $duckdb;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export config.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
/**
|
||||
* ZenTaoPHP的DuckDB类。
|
||||
* The dao and sql class file of ZenTaoPHP framework.
|
||||
*
|
||||
* The author disclaims copyright to this source code. In place of
|
||||
* a legal notice, here is a blessing:
|
||||
*
|
||||
* May you do good and not evil.
|
||||
* May you find forgiveness for yourself and forgive others.
|
||||
* May you share freely, never taking more than you give.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DuckDB类。
|
||||
* DuckDB, data access object.
|
||||
*
|
||||
* @package framework
|
||||
*/
|
||||
class duckdb
|
||||
{
|
||||
/**
|
||||
* 全局变量$baseRoot
|
||||
* The global basePath variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $baseRoot;
|
||||
|
||||
/**
|
||||
* 全局变量$tmpRoot
|
||||
* The global tmpRoot variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $tmpRoot;
|
||||
|
||||
/**
|
||||
* 全局变量$prefix
|
||||
* The global prefix variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $prefix;
|
||||
|
||||
/**
|
||||
* 全局变量$sql
|
||||
* The global sql variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $sql;
|
||||
|
||||
/**
|
||||
* 全局变量$binPath
|
||||
* The global binPath variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $binPath;
|
||||
|
||||
/**
|
||||
* 全局变量$tmpPath
|
||||
* The global tmpPath variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $tmpPath;
|
||||
|
||||
/**
|
||||
* 全局变量$fields
|
||||
* The global fields variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* 全局变量$tables
|
||||
* The global fields variable.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $tables;
|
||||
|
||||
/**
|
||||
* 构造方法。
|
||||
* The construct method.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
global $app, $config;
|
||||
$this->prefix = $config->db->prefix;
|
||||
$this->baseRoot = $app->getBasePath();
|
||||
$this->tmpRoot = $app->getTmpRoot();
|
||||
|
||||
$this->setBinPath();
|
||||
$this->setTmpPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 duckdb 可执行文件路径。
|
||||
* Set duckdb bin path.
|
||||
*
|
||||
* @access public
|
||||
* @return void.
|
||||
*/
|
||||
public function setBinPath()
|
||||
{
|
||||
$this->binPath = $this->baseRoot . 'bin' . DS . 'duckdb' . DS . 'duckdb';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 duckdb tmp parquet 文件路径。
|
||||
* Set duckdb tmp parquet file path.
|
||||
*
|
||||
* @access public
|
||||
* @return void.
|
||||
*/
|
||||
public function setTmpPath($module = 'bi')
|
||||
{
|
||||
$this->tmpPath = $this->tmpRoot . 'duckdb' . DS . $module . DS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query 方法。
|
||||
* Query function.
|
||||
*
|
||||
* @param string sql
|
||||
* @access public
|
||||
* @return this.
|
||||
*/
|
||||
public function query($sql = '')
|
||||
{
|
||||
$sqlparser = dirname(dirname(__FILE__)) . '/sqlparser/sqlparser.class.php';
|
||||
include_once $sqlparser;
|
||||
|
||||
$parser = new sqlparser($sql);
|
||||
$statement = $parser->statements[0];
|
||||
if(isset($statement->bodyParser->statements[0])) $statement = $statement->bodyParser->statements[0];
|
||||
$this->fields = $this->getFields($statement);
|
||||
$this->tables = $this->getTables($statement);
|
||||
|
||||
$sql = $this->replaceBackQuote($sql);
|
||||
$sql = $this->replaceTable2Parquet($sql);
|
||||
$sql = $this->standLimit($sql);
|
||||
|
||||
$this->sql = $sql;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将mysql的``替换为duckdb可执行的""。
|
||||
* Replace ` to ".
|
||||
*
|
||||
* @param string sql
|
||||
* @access public
|
||||
* @return sql.
|
||||
*/
|
||||
private function replaceBackQuote($sql)
|
||||
{
|
||||
$sql = trim($sql);
|
||||
$sql = trim($sql, ';');
|
||||
$sql = str_replace(array('`', '"'), array('', '\"'), $sql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换sql语句中的表为parquet文件路径。
|
||||
* Replace sql table to parquet file path.
|
||||
*
|
||||
* @param string sql
|
||||
* @access public
|
||||
* @return sql.
|
||||
*/
|
||||
private function replaceTable2Parquet($sql)
|
||||
{
|
||||
if(empty($this->tables)) return $sql;
|
||||
|
||||
foreach($this->tables as $table)
|
||||
{
|
||||
$pattern = "/\b{$table}\b/";
|
||||
$replace = "'{$this->tmpPath}{$table}.parquet'";
|
||||
|
||||
$sql = preg_replace($pattern, $replace, $sql);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sql中的字段。
|
||||
* Get fields form sqlparser statment.
|
||||
*
|
||||
* @param object $statment
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getFields(object $statement)
|
||||
{
|
||||
$fields = array();
|
||||
if($statement->expr)
|
||||
{
|
||||
foreach($statement->expr as $fieldInfo)
|
||||
{
|
||||
$column = $fieldInfo->column;
|
||||
$alias = $fieldInfo->alias;
|
||||
$fields[$column] = array('column' => $column, 'alias' => $alias);
|
||||
}
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sql中的表名。
|
||||
* Get tables form sqlparser statment.
|
||||
*
|
||||
* @param object $statment
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
private function getTables(object $statement)
|
||||
{
|
||||
$tables = array();
|
||||
if($statement->from)
|
||||
{
|
||||
foreach($statement->from as $fromInfo)
|
||||
{
|
||||
if($fromInfo->table)
|
||||
{
|
||||
$tables[] = $fromInfo->table;
|
||||
}
|
||||
elseif($fromInfo->subquery)
|
||||
{
|
||||
$parser = new sqlparser($fromInfo->expr);
|
||||
$subTables = $this->getTables($parser->statements[0]);
|
||||
$tables = array_merge($tables, $subTables);
|
||||
}
|
||||
}
|
||||
}
|
||||
if($statement->join)
|
||||
{
|
||||
foreach($statement->join as $joinInfo)
|
||||
{
|
||||
if($joinInfo->expr->table)
|
||||
{
|
||||
$tables[] = $joinInfo->expr->table;
|
||||
}
|
||||
elseif($joinInfo->expr->subquery)
|
||||
{
|
||||
$parser = new sqlparser($joinInfo->expr->expr);
|
||||
$subTables = $this->getTables($parser->statements[0]);
|
||||
$tables = array_merge($tables, $subTables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_filter(array_unique($tables));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将LIMIT语句替换为duckdb可执行的格式。
|
||||
* Standard LIMIT syntax.
|
||||
*
|
||||
* @param string sql
|
||||
* @access public
|
||||
* @return sql.
|
||||
*/
|
||||
private function standLimit($sql)
|
||||
{
|
||||
// 匹配 "LIMIT x, y" 并替换为 "LIMIT y OFFSET x"
|
||||
$limitpattern = '/LIMIT\s+(\d+)\s*,\s*(\d+)/i';
|
||||
$replacement = 'LIMIT $2 OFFSET $1';
|
||||
|
||||
$sql = preg_replace($limitpattern, $replacement, $sql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试执行duckdb sql并获取返回结果。
|
||||
* Try to exec duckdb sql.
|
||||
*
|
||||
* @access public
|
||||
* @return this.
|
||||
*/
|
||||
public function getResult()
|
||||
{
|
||||
$this->checkFieldNamesMatch();
|
||||
|
||||
$exec = "$this->binPath :memory: \"$this->sql\" -json 2>&1";
|
||||
$output = shell_exec($exec);
|
||||
|
||||
if(empty($output)) $output = '';
|
||||
$rows = json_decode($output);
|
||||
/* 有内容但是 json 解析失败,说明是报错。*/
|
||||
if($output and !$rows)
|
||||
{
|
||||
return throw new Exception($output);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $rows ? $rows : array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查sql返回的字段不能与表名同名。
|
||||
* Check sql fields can not same as table name.
|
||||
*
|
||||
* @access public
|
||||
* @return this.
|
||||
*/
|
||||
public function checkFieldNamesMatch()
|
||||
{
|
||||
$flipTable = array_flip($this->tables);
|
||||
foreach($this->fields as $field)
|
||||
{
|
||||
if(isset($flipTable[$field['column']])) return throw new Exception("sql fields can not named {$field['column']}");
|
||||
if(isset($flipTable[$field['alias']])) return throw new Exception("sql fields can not named {$field['alias']}");
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------- Fetch相关方法(Fetch related methods) -------------------//
|
||||
|
||||
/**
|
||||
* 获取一个记录。
|
||||
* Fetch one record.
|
||||
*
|
||||
* @access public
|
||||
* @return object|mixed
|
||||
*/
|
||||
public function fetch()
|
||||
{
|
||||
$rows = $this->getResult();
|
||||
return $rows ? current($rows) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有记录。
|
||||
* Fetch all records.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->getResult();
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,333 @@ require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
class sqlparser
|
||||
{
|
||||
/**
|
||||
* Parser object of SqlParser.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $parser;
|
||||
|
||||
/**
|
||||
* Statements of parser.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $statements;
|
||||
|
||||
/**
|
||||
* First statement.
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $statement;
|
||||
|
||||
/**
|
||||
* Count statements.
|
||||
*
|
||||
* @var int
|
||||
* @access public
|
||||
*/
|
||||
public $statementsCount = 0;
|
||||
|
||||
/**
|
||||
* Is first statemnt select type.
|
||||
*
|
||||
* @var bool
|
||||
* @access public
|
||||
*/
|
||||
public $isSelect = false;
|
||||
|
||||
/**
|
||||
* DAO
|
||||
*
|
||||
* @var object
|
||||
* @access public
|
||||
*/
|
||||
public $dao = null;
|
||||
|
||||
/**
|
||||
* Origin tables.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $originTables = array();
|
||||
|
||||
/**
|
||||
* Columns.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $columns = array();
|
||||
|
||||
/**
|
||||
* Tables.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public $tables = array();
|
||||
|
||||
public function __construct($query)
|
||||
{
|
||||
$this->parser = new PhpMyAdmin\SqlParser\Parser($query);
|
||||
$this->statements = $this->parser->statements;
|
||||
$query = $this->skipLineBreak($query);
|
||||
|
||||
$this->parser = new PhpMyAdmin\SqlParser\Parser($query);
|
||||
$this->statements = $this->parser->statements;
|
||||
$this->statementsCount = count($this->statements);
|
||||
$this->statement = $this->statementsCount > 0 ? current($this->statements) : null;
|
||||
|
||||
$this->isSelect = $this->statement instanceof PhpMyAdmin\SqlParser\Statements\SelectStatement === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse statement.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function parseStatement()
|
||||
{
|
||||
$this->columns = $this->parseColumns();
|
||||
$this->tables = $this->parseTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Match columns with table.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function matchColumnsWithTable()
|
||||
{
|
||||
if(count($this->tables) == 1) return $this->combineSingleTable();
|
||||
return $this->combineMultipleTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine single table to columns.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function combineSingleTable()
|
||||
{
|
||||
$combineColumns = array();
|
||||
$fromTable = current($this->tables);
|
||||
foreach($this->columns as $columnName => $column)
|
||||
{
|
||||
$column['table'] = array_merge($fromTable, array('column' => $column['origin']));
|
||||
|
||||
$combineColumns[$columnName] = $column;
|
||||
}
|
||||
|
||||
return $combineColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine multiple table to columns.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function combineMultipleTable()
|
||||
{
|
||||
foreach($this->columns as $columnName => $column)
|
||||
{
|
||||
$column['table'] = $this->searchTables($column['table'], $this->tables, $column['origin']);
|
||||
$combineColumns[$columnName] = $column;
|
||||
}
|
||||
|
||||
return $combineColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search table from origin tables.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $tables
|
||||
* @param string $column
|
||||
* @access public
|
||||
* @return string|false
|
||||
*/
|
||||
public function searchTables($tableName, $tables, $column)
|
||||
{
|
||||
/* 如果能使用别名匹配上,那么直接返回。*/
|
||||
/* If it can be matched using an alias, then it returns. */
|
||||
foreach($tables as $table) if($tableName == $table['alias']) return array_merge($table, array('column' => $column));
|
||||
|
||||
/* 如果匹配不上,则字段没有使用别名进行限制,那么需要通过字段去遍历所有表。*/
|
||||
/* If it doesn't match, then the field is not aliased, and you need to iterate over all tables using the field. */
|
||||
foreach($tables as $table)
|
||||
{
|
||||
$isTable = $table['isTable'];
|
||||
$originTable = $table['originTable'];
|
||||
|
||||
/* 如果是原始表,并且列在原始表中存在,那么返回这个表。*/
|
||||
/* If it is the original table and the column exists in the original table, then the table is returned. */
|
||||
if($isTable && $this->columnExistInOriginTable($originTable, $column)) return array_merge($table, array('column' => $column));
|
||||
/* 如果不是原始表,并且列在子句中存在,那么返回子句中这个字段对应的表。*/
|
||||
/* If it is not the original table and the column exists in the clause, then the table corresponding to the field in the clause is returned. */
|
||||
if(!$isTable && isset($originTable[$column])) return array_merge($originTable[$column]['table'], array('column' => $originTable[$column]['origin']));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse columns.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function parseColumns()
|
||||
{
|
||||
$fields = array();
|
||||
foreach($this->statement->expr as $expr)
|
||||
{
|
||||
/* 获取查询数据后真正展示出来的列名 */
|
||||
$columnName = empty($expr->alias) ? $expr->column : $expr->alias;
|
||||
|
||||
$fields[$columnName] = array('origin' => $expr->column, 'table' => $expr->table);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tables.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function parseTables()
|
||||
{
|
||||
$from = current($this->statement->from);
|
||||
$joins = $this->statement->join;
|
||||
|
||||
$tables = array();
|
||||
$tables[] = $this->parseTable($from, 'from');
|
||||
|
||||
foreach($joins as $join) $tables[] = $this->parseTable($join->expr, 'join');
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse table.
|
||||
*
|
||||
* @param object $expr
|
||||
* @param string $type
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function parseTable($expr, $type)
|
||||
{
|
||||
$isTable = empty($expr->subquery);
|
||||
|
||||
$table = array('alias' => $expr->alias, 'isTable' => $isTable, 'type' => $type);
|
||||
$table['originTable'] = $this->getOriginTable($expr->expr, $isTable);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get origin table from table name or expr.
|
||||
*
|
||||
* @param string $table
|
||||
* @param bool $isTable
|
||||
* @access public
|
||||
* @return string|array
|
||||
*/
|
||||
public function getOriginTable($table, $isTable)
|
||||
{
|
||||
if(!$isTable)
|
||||
{
|
||||
$parser = new sqlparser($table);
|
||||
$parser->setDAO($this->dao);
|
||||
$parser->parseStatement();
|
||||
return $parser->matchColumnsWithTable();
|
||||
}
|
||||
|
||||
$this->storeOriginTable($table);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge column exist in origin table or not.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function columnExistInOriginTable($table, $column)
|
||||
{
|
||||
$originTable = $this->getOriginTableColumns($table);
|
||||
if(!$originTable) return false;
|
||||
|
||||
return isset($originTable[$column]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get origin table columns.
|
||||
*
|
||||
* @param string $table
|
||||
* @access public
|
||||
* @return array|null
|
||||
*/
|
||||
public function getOriginTableColumns($table)
|
||||
{
|
||||
$originTables = $this->originTables;
|
||||
return isset($originTables[$table]) ? $originTables[$table] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store origin table.
|
||||
*
|
||||
* @param string $table
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function storeOriginTable($table)
|
||||
{
|
||||
if(!isset($this->originTables[$table])) $this->originTables[$table] = $this->dao->descTable($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dao.
|
||||
*
|
||||
* @param object $dao
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function setDAO($dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip line break in sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function skipLineBreak($sql)
|
||||
{
|
||||
$sql = str_replace("\n\t", " ", $sql);
|
||||
$sql = str_replace("\t\n", " ", $sql);
|
||||
$sql = str_replace("\n\r", " ", $sql);
|
||||
$sql = str_replace("\r\n", " ", $sql);
|
||||
$sql = str_replace("\r", " ", $sql);
|
||||
$sql = str_replace("\n", " ", $sql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class moduleMenu extends wg
|
||||
|
||||
protected static array $defineBlocks = array
|
||||
(
|
||||
'header' => array(),
|
||||
'footer' => array()
|
||||
'header' => array(),
|
||||
'footer' => array()
|
||||
);
|
||||
|
||||
public static function getPageCSS(): ?string
|
||||
|
||||
@@ -86,3 +86,258 @@ $pivots['100'] = array("root" => 3, "name" => "Bug", "grade" => 2);
|
||||
|
||||
$config->bi->builtin->modules->charts = $charts;
|
||||
$config->bi->builtin->modules->pivots = $pivots;
|
||||
|
||||
$config->bi->duckSQLTemp = <<<EOT
|
||||
LOAD '{EXTENSIONPATH}';
|
||||
ATTACH 'host={HOST} user={USER} password={PASSWORD} port={PORT} database={DATABASE}' as mysqldb(TYPE MYSQL);
|
||||
USE mysqldb;
|
||||
{COPYSQL}
|
||||
EOT;
|
||||
|
||||
$config->bi->drivers = array('mysql', 'duckdb');
|
||||
|
||||
$config->bi->columnTypes = new stdclass();
|
||||
$config->bi->columnTypes->mysql['TINY'] = 'number';
|
||||
$config->bi->columnTypes->mysql['SHORT'] = 'number';
|
||||
$config->bi->columnTypes->mysql['LONG'] = 'number';
|
||||
$config->bi->columnTypes->mysql['FLOAT'] = 'number';
|
||||
$config->bi->columnTypes->mysql['DOUBLE'] = 'number';
|
||||
$config->bi->columnTypes->mysql['TIMESTAMP'] = 'string';
|
||||
$config->bi->columnTypes->mysql['LONGLONG'] = 'string';
|
||||
$config->bi->columnTypes->mysql['INT24'] = 'number';
|
||||
$config->bi->columnTypes->mysql['DATE'] = 'date';
|
||||
$config->bi->columnTypes->mysql['TIME'] = 'string';
|
||||
$config->bi->columnTypes->mysql['DATETIME'] = 'date';
|
||||
$config->bi->columnTypes->mysql['YEAR'] = 'date';
|
||||
$config->bi->columnTypes->mysql['ENUM'] = 'string';
|
||||
$config->bi->columnTypes->mysql['SET'] = 'string';
|
||||
$config->bi->columnTypes->mysql['TINYBLOB'] = 'string';
|
||||
$config->bi->columnTypes->mysql['MEDIUMBLOB'] = 'string';
|
||||
$config->bi->columnTypes->mysql['LONG_BLOB'] = 'string';
|
||||
$config->bi->columnTypes->mysql['BLOB'] = 'string';
|
||||
$config->bi->columnTypes->mysql['VAR_STRING'] = 'string';
|
||||
$config->bi->columnTypes->mysql['STRING'] = 'string';
|
||||
$config->bi->columnTypes->mysql['NULL'] = 'null';
|
||||
$config->bi->columnTypes->mysql['NEWDATE'] = 'date';
|
||||
$config->bi->columnTypes->mysql['INTERVAL'] = 'string';
|
||||
$config->bi->columnTypes->mysql['GEOMETRY'] = 'string';
|
||||
$config->bi->columnTypes->mysql['NEWDECIMAL'] = 'number';
|
||||
|
||||
/* Dameng native_type. */
|
||||
$config->bi->columnTypes->mysql['int'] = 'number';
|
||||
$config->bi->columnTypes->mysql['varchar'] = 'string';
|
||||
$config->bi->columnTypes->mysql['text'] = 'string';
|
||||
$config->bi->columnTypes->mysql['timestamp'] = 'string';
|
||||
$config->bi->columnTypes->mysql['date'] = 'date';
|
||||
$config->bi->columnTypes->mysql['time'] = 'string';
|
||||
$config->bi->columnTypes->mysql['double'] = 'number';
|
||||
$config->bi->columnTypes->mysql['number'] = 'number';
|
||||
$config->bi->columnTypes->mysql['bigint'] = 'number';
|
||||
|
||||
/* DuckDB native_type. */
|
||||
$config->bi->columnTypes->duckdb['BIGINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['UBIGINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['HUGEINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['UHUGEINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['INTEGER'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['UINTEGER'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['SMALLINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['USMALLINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['TINYINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['UTINYINT'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['REAL'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['DOUBLE'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['BOOLEAN'] = 'number';
|
||||
$config->bi->columnTypes->duckdb['BLOB'] = 'string';
|
||||
$config->bi->columnTypes->duckdb['VARCHAR'] = 'string';
|
||||
$config->bi->columnTypes->duckdb['DATE'] = 'date';
|
||||
$config->bi->columnTypes->duckdb['TIMESTAMP'] = 'date';
|
||||
$config->bi->columnTypes->duckdb['TIME'] = 'date';
|
||||
|
||||
$config->bi->columnTypes->INTEGER = 'number';
|
||||
$config->bi->columnTypes->UINTEGER = 'number';
|
||||
$config->bi->columnTypes->UTINYINT = 'number';
|
||||
$config->bi->columnTypes->SMALLINT = 'number';
|
||||
$config->bi->columnTypes->FLOAT = 'number';
|
||||
$config->bi->columnTypes->BOOLEAN = 'number';
|
||||
$config->bi->columnTypes->VARCHAR = 'string';
|
||||
$config->bi->columnTypes->TIMESTAMP = 'date';
|
||||
$config->bi->columnTypes->DATE = 'date';
|
||||
|
||||
$config->bi->duckdb = new stdclass();
|
||||
$config->bi->duckdb->tables = array();
|
||||
$config->bi->duckdb->tables['action'] = <<<EOT
|
||||
SELECT id,objectType,objectID,product,project,execution,actor,action,date,read,vision,efforted FROM zt_action
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['account'] = <<<EOT
|
||||
SELECT * FROM zt_account
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['attend'] = <<<EOT
|
||||
SELECT * FROM zt_attend
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['bug'] = <<<EOT
|
||||
SELECT * FROM zt_bug
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['build'] = <<<EOT
|
||||
SELECT * FROM zt_build
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['burn'] = <<<EOT
|
||||
SELECT * FROM zt_burn
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['case'] = <<<EOT
|
||||
SELECT * FROM zt_case
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['casestep'] = <<<EOT
|
||||
SELECT * FROM zt_casestep
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['config'] = <<<EOT
|
||||
SELECT * FROM zt_config
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['demand'] = <<<EOT
|
||||
SELECT * FROM zt_demand
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['dept'] = <<<EOT
|
||||
SELECT * FROM zt_dept
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['doc'] = <<<EOT
|
||||
SELECT * FROM zt_doc
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['effort'] = <<<EOT
|
||||
SELECT * FROM zt_effort
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['feedback'] = <<<EOT
|
||||
SELECT * FROM zt_feedback
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['file'] = <<<EOT
|
||||
SELECT * FROM zt_file
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['group'] = <<<EOT
|
||||
SELECT * FROM zt_group
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['history'] = <<<EOT
|
||||
SELECT id,action,field FROM zt_history
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['issue'] = <<<EOT
|
||||
SELECT * FROM zt_issue
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['meeting'] = <<<EOT
|
||||
SELECT * FROM zt_meeting
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['metric'] = <<<EOT
|
||||
SELECT * FROM zt_metric
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['metriclib'] = <<<EOT
|
||||
SELECT * FROM zt_metriclib
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['module'] = <<<EOT
|
||||
SELECT * FROM zt_module
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['opportunity'] = <<<EOT
|
||||
SELECT * FROM zt_opportunity
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['pivot'] = <<<EOT
|
||||
SELECT * FROM zt_pivot
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['product'] = <<<EOT
|
||||
SELECT * FROM zt_product
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['productplan'] = <<<EOT
|
||||
SELECT * FROM zt_productplan
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['planstory'] = <<<EOT
|
||||
SELECT * FROM zt_planstory
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['project'] = <<<EOT
|
||||
SELECT * FROM zt_project
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['projectcase'] = <<<EOT
|
||||
SELECT * FROM zt_projectcase
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['projectproduct'] = <<<EOT
|
||||
SELECT * FROM zt_projectproduct
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['projectstory'] = <<<EOT
|
||||
SELECT * FROM zt_projectstory
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['release'] = <<<EOT
|
||||
SELECT * FROM zt_release
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['risk'] = <<<EOT
|
||||
SELECT * FROM zt_risk
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['story'] = <<<EOT
|
||||
SELECT * FROM zt_story
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['task'] = <<<EOT
|
||||
SELECT * FROM zt_task
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['team'] = <<<EOT
|
||||
SELECT * FROM zt_team
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['testreport'] = <<<EOT
|
||||
SELECT * FROM zt_testreport
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['testresult'] = <<<EOT
|
||||
SELECT * FROM zt_testresult
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['testtask'] = <<<EOT
|
||||
SELECT * FROM zt_testtask
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['ticket'] = <<<EOT
|
||||
SELECT * FROM zt_ticket
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['todo'] = <<<EOT
|
||||
SELECT * FROM zt_todo
|
||||
EOT;
|
||||
$config->bi->duckdb->tables['user'] = <<<EOT
|
||||
SELECT * FROM zt_user
|
||||
EOT;
|
||||
|
||||
$config->bi->duckdb->ztvtables['dayactions'] = <<<EOT
|
||||
SELECT * FROM ztv_dayactions
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['dayuserlogin'] = <<<EOT
|
||||
SELECT * FROM ztv_dayuserlogin
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['dayeffort'] = <<<EOT
|
||||
SELECT * FROM ztv_dayeffort
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['daystoryopen'] = <<<EOT
|
||||
SELECT * FROM ztv_daystoryopen
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['daystoryclose'] = <<<EOT
|
||||
SELECT * FROM ztv_daystoryclose
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['daytaskopen'] = <<<EOT
|
||||
SELECT * FROM ztv_daytaskopen
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['daytaskfinish'] = <<<EOT
|
||||
SELECT * FROM ztv_daytaskfinish
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['daybugopen'] = <<<EOT
|
||||
SELECT * FROM ztv_daybugopen
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['daybugresolve'] = <<<EOT
|
||||
SELECT * FROM ztv_daybugresolve
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['productstories'] = <<<EOT
|
||||
SELECT * FROM ztv_productstories
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['productbugs'] = <<<EOT
|
||||
SELECT * FROM ztv_productbugs
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['projectsummary'] = <<<EOT
|
||||
SELECT * FROM ztv_projectsummary
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['executionsummary'] = <<<EOT
|
||||
SELECT * FROM ztv_executionsummary
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['projectstories'] = <<<EOT
|
||||
SELECT * FROM ztv_projectstories
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['projectbugs'] = <<<EOT
|
||||
SELECT * FROM ztv_projectbugs
|
||||
EOT;
|
||||
$config->bi->duckdb->ztvtables['projectteams'] = <<<EOT
|
||||
SELECT * FROM ztv_projectteams
|
||||
EOT;
|
||||
|
||||
+1118
-1132
File diff suppressed because it is too large
Load Diff
+606
-340
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The control file of branch of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2024 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
|
||||
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @author Chenxuan Song <songchenxuan@cnezsoft.com>
|
||||
* @package bi
|
||||
* @version $Id$
|
||||
* @link https://www.zentao.net
|
||||
*/
|
||||
class bi extends control
|
||||
{
|
||||
/**
|
||||
* 使用 duckDB 生成最新的 parquet 文件。
|
||||
* Use duckDB gen lastest parquet.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function syncParquetFile()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$duckdb = $this->bi->getDuckDBPath();
|
||||
if(!$duckdb)
|
||||
{
|
||||
echo("DuckDB bin path not exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
$duckdbTmpPath = $this->bi->getDuckDBTmpDir();
|
||||
if(!$duckdbTmpPath)
|
||||
{
|
||||
echo("Create DuckDB tmp dir permission denied.");
|
||||
return;
|
||||
}
|
||||
|
||||
$copySQL = $this->bi->prepareCopySQL($duckdbTmpPath);
|
||||
$command = $this->bi->prepareSyncCommand($duckdb->bin, $duckdb->extension, $copySQL);
|
||||
|
||||
$output = shell_exec($command);
|
||||
$endTime = microtime(true);
|
||||
$runTime = $endTime - $startTime;
|
||||
echo "$runTime \n";
|
||||
|
||||
if(empty($output))
|
||||
{
|
||||
echo('success');
|
||||
return;
|
||||
}
|
||||
|
||||
echo($output);
|
||||
}
|
||||
}
|
||||
+566
-5
@@ -90,6 +90,154 @@ class biModel extends model
|
||||
return array_filter(array_unique($tables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to explain sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $driver mysql|duckdb
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function explainSQL($sql, $driver = 'mysql')
|
||||
{
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
|
||||
$prefixSQL = $driver == 'mysql' ? 'EXPLAIN' : 'PRAGMA enable_profiling=json; EXPLAIN ANALYZE';
|
||||
try
|
||||
{
|
||||
$rows = $dbh->query("$prefixSQL $sql")->fetchAll();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$message = preg_replace("/\r|\n|\t/", "", $e->getMessage());
|
||||
$message = strip_tags($message);
|
||||
return array('result' => 'fail', 'message' => $message);
|
||||
}
|
||||
|
||||
return array('result' => 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query a sql with driver.
|
||||
*
|
||||
* @param string $driver mysql|duckdb
|
||||
* @param string $sql
|
||||
* @param bool $fetchAll
|
||||
* @access public
|
||||
* @return array|string
|
||||
*/
|
||||
public function queryWithDriver($driver, $sql, $fetchAll = true)
|
||||
{
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
|
||||
if($fetchAll) $results = $dbh->query($sql)->fetchAll();
|
||||
else $results = $dbh->query($sql)->fetch();
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to explain sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $limitSql
|
||||
* @param string $driver mysql|duckdb
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function querySQL($sql, $limitSql, $driver = 'mysql')
|
||||
{
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
|
||||
try
|
||||
{
|
||||
if($driver == 'mysql')
|
||||
{
|
||||
$rows = $dbh->query($limitSql)->fetchAll();
|
||||
$count = $dbh->query("SELECT FOUND_ROWS() as count")->fetch();
|
||||
$rowsCount = $count->count;
|
||||
}
|
||||
elseif($driver == 'duckdb')
|
||||
{
|
||||
$rows = $dbh->query($limitSql)->fetchAll();
|
||||
$allRows = $dbh->query("SELECT COUNT(1) as count FROM ( $sql )")->fetch();
|
||||
$rowsCount = $allRows->count;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$message = preg_replace("/\r|\n|\t/", "", $e->getMessage());
|
||||
$message = strip_tags($message);
|
||||
return array('result' => 'fail', 'message' => $message);
|
||||
}
|
||||
|
||||
return array('result' => 'success', 'rows' => $rows, 'rowsCount' => $rowsCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sql result columns.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $driver mysql|duckdb
|
||||
* @access public
|
||||
* @return array|false
|
||||
*/
|
||||
public function getColumns(string $sql, $driver = 'mysql'): array|false
|
||||
{
|
||||
if(!in_array($driver, $this->config->bi->drivers)) return false;
|
||||
|
||||
if($driver == 'mysql')
|
||||
{
|
||||
$columns = $this->dao->getColumns($sql);
|
||||
}
|
||||
else
|
||||
{
|
||||
$dbh = $this->app->loadDriver('duckdb');
|
||||
$columns = $dbh->query("DESCRIBE $sql")->fetchAll();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach($columns as $column)
|
||||
{
|
||||
$column = (array)$column;
|
||||
|
||||
$name = $driver == 'mysql' ? $column['name'] : $column['column_name'];
|
||||
$nativeType = $driver == 'mysql' ? $column['native_type'] : $column['column_type'];
|
||||
|
||||
$result[$name] = array('name' => $name, 'native_type' => $nativeType);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表的字段类型。
|
||||
* Get table data.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $driverName mysql|duckdb
|
||||
* @param array $columns
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function getColumnsType(string $sql, string $driverName = 'mysql', array $columns = array()): object
|
||||
{
|
||||
if(empty($columns)) $columns = $this->getColumns($sql, $driverName);
|
||||
|
||||
$columnTypes = new stdclass();
|
||||
foreach($columns as $column)
|
||||
{
|
||||
$field = $column['name'];
|
||||
$nativeType = $column['native_type'];
|
||||
$type = $this->config->bi->columnTypes->$driverName[$nativeType];
|
||||
|
||||
if(isset($columnTypes->$field)) $field = $column['table'] . $field;
|
||||
$columnTypes->$field = $type;
|
||||
}
|
||||
|
||||
return $columnTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object options.
|
||||
*
|
||||
@@ -192,13 +340,15 @@ class biModel extends model
|
||||
* @param string $sql
|
||||
* @param string $keyField
|
||||
* @param string $valueField
|
||||
* @param string $driver
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getOptionsFromSql(string $sql, string $keyField, string $valueField): array
|
||||
public function getOptionsFromSql(string $sql, $driver, string $keyField, string $valueField): array
|
||||
{
|
||||
$options = array();
|
||||
$cols = $this->dbh->query($sql)->fetchAll();
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
$cols = $dbh->query($sql)->fetchAll();
|
||||
$sample = current($cols);
|
||||
|
||||
if(!isset($sample->$keyField) or !isset($sample->$valueField)) return $options;
|
||||
@@ -276,7 +426,7 @@ class biModel extends model
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function getMultiData($settings, $defaultSql, $filters, $sort = false)
|
||||
public function getMultiData($settings, $defaultSql, $filters, $driver, $sort = false)
|
||||
{
|
||||
$this->loadModel('chart');
|
||||
|
||||
@@ -328,7 +478,7 @@ class biModel extends model
|
||||
$sql .= " where $whereStr";
|
||||
}
|
||||
$sql .= " group by $groupBySql";
|
||||
$rows = $this->dao->query($sql)->fetchAll();
|
||||
$rows = $this->queryWithDriver($driver, $sql);
|
||||
$stat = $this->processRows($rows, $date, $group, $metric);
|
||||
|
||||
$maxCount = 50;
|
||||
@@ -385,7 +535,7 @@ class biModel extends model
|
||||
return $stat;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* 准备内置的图表sql语句。
|
||||
* Prepare builtin chart sql.
|
||||
*
|
||||
@@ -578,6 +728,417 @@ class biModel extends model
|
||||
return $screenSQLs;
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取DuckDB的可执行文件路径。
|
||||
* Get DcukDB path.
|
||||
*
|
||||
* @access public
|
||||
* @return object|false
|
||||
*/
|
||||
public function getDuckDBPath()
|
||||
{
|
||||
$binPath = $this->app->getBasePath() . 'bin' . DS . 'duckdb' . DS;
|
||||
$file = $binPath . 'duckdb';
|
||||
$extension = $binPath . 'mysql_scanner.duckdb_extension';
|
||||
|
||||
if(!file_exists($file) && !file_exists($extension) && !is_executable($file)) return false;
|
||||
|
||||
return (object)array('bin' => $file, 'extension' => $extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DuckDB临时目录。
|
||||
* Get DuckDB temp directory.
|
||||
*
|
||||
* @access public
|
||||
* @return string|false
|
||||
*/
|
||||
public function getDuckDBTmpDir()
|
||||
{
|
||||
$duckdbTmpPath = $this->app->getTmpRoot() . 'duckdb' . DS . 'bi' . DS;
|
||||
if(!is_dir($duckdbTmpPath) && !mkdir($duckdbTmpPath, 0755, true)) return false;
|
||||
|
||||
return $duckdbTmpPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备同步数据库所需的复制SQL。
|
||||
* Prepare copy SQL for sync.
|
||||
*
|
||||
* @param string $duckdbTmpPath
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function prepareCopySQL($duckdbTmpPath)
|
||||
{
|
||||
$tables = $this->config->bi->duckdb->tables;
|
||||
$ztvtables = $this->config->bi->duckdb->ztvtables;
|
||||
if(empty($tables)) return '';
|
||||
|
||||
$tablePrefix = $this->config->db->prefix;
|
||||
$ztvPrefix = 'ztv_';
|
||||
|
||||
$copySQL = '';
|
||||
foreach($tables as $table => $sql)
|
||||
{
|
||||
$table = $tablePrefix . $table;
|
||||
$sql = str_replace('zt_', $tablePrefix, $sql);
|
||||
|
||||
$tablePath = $duckdbTmpPath . $table;
|
||||
$copySQL .= "COPY ($sql) TO '$tablePath.parquet';\n";
|
||||
}
|
||||
|
||||
foreach($ztvtables as $table => $sql)
|
||||
{
|
||||
$table = $ztvPrefix . $table;
|
||||
|
||||
$tablePath = $duckdbTmpPath . $table;
|
||||
$copySQL .= "COPY ($sql) TO '$tablePath.parquet';\n";
|
||||
}
|
||||
|
||||
return $copySQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备同步命令。
|
||||
* Prepare sync command.
|
||||
*
|
||||
* @param string $binPath
|
||||
* @param string $extensionPath
|
||||
* @param string $copySQL
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function prepareSyncCommand($binPath, $extensionPath, $copySQL)
|
||||
{
|
||||
$sqlContent = $this->config->bi->duckSQLTemp;
|
||||
$dbConfig = $this->config->db;
|
||||
$variables = array(
|
||||
'{EXTENSIONPATH}' => $extensionPath,
|
||||
'{DATABASE}' => $dbConfig->name,
|
||||
'{USER}' => $dbConfig->user,
|
||||
'{PASSWORD}' => $dbConfig->password,
|
||||
'{HOST}' => $dbConfig->host,
|
||||
'{PORT}' => $dbConfig->port,
|
||||
'{COPYSQL}' => $copySQL
|
||||
);
|
||||
|
||||
foreach($variables as $key => $value)
|
||||
{
|
||||
$sqlContent = str_replace($key, $value, $sqlContent);
|
||||
}
|
||||
|
||||
return "$binPath :memory: \"$sqlContent\" 2>&1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Process filter variables in sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param array $filters
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function processVars($sql, $filters = array())
|
||||
{
|
||||
foreach($filters as $index => $filter)
|
||||
{
|
||||
if(empty($filter['default'])) continue;
|
||||
if(!isset($filter['from']) || $filter['from'] != 'query') continue;
|
||||
|
||||
$filters[$index]['default'] = $this->loadModel('pivot')->processDateVar($filter['default']);
|
||||
}
|
||||
$sql = $this->loadModel('chart')->parseSqlVars($sql, $filters);
|
||||
$sql = trim($sql, ';');
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build statement object from sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function sql2Statement($sql)
|
||||
{
|
||||
$this->app->loadClass('sqlparser', true);
|
||||
$parser = new sqlparser($sql);
|
||||
|
||||
if($parser->statementsCount == 0) return $this->lang->dataview->empty;
|
||||
if($parser->statementsCount > 1) return $this->lang->dataview->onlyOne;
|
||||
|
||||
if(!$parser->isSelect) return $this->lang->dataview->allowSelect;
|
||||
|
||||
return $parser->statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function parseSql(string $sql): array
|
||||
{
|
||||
$this->app->loadClass('sqlparser', true);
|
||||
$parser = new sqlparser($sql);
|
||||
$parser->setDAO($this->dao);
|
||||
$parser->parseStatement();
|
||||
|
||||
return $parser->matchColumnsWithTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return string|true
|
||||
*/
|
||||
public function validateSql($sql)
|
||||
{
|
||||
$this->loadModel('dataview');
|
||||
|
||||
$driver = 'mysql';
|
||||
|
||||
if(empty($sql)) return $this->lang->dataview->empty;
|
||||
try
|
||||
{
|
||||
$rows = $this->dbh->query("EXPLAIN $sql")->fetchAll();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$sqlColumns = $this->getColumns($sql, $driver);
|
||||
list($isUnique, $repeatColumn) = $this->dataview->checkUniColumn($sql, $driver, true, $sqlColumns);
|
||||
|
||||
if(!$isUnique) return sprintf($this->lang->dataview->duplicateField, implode(',', $repeatColumn));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare pager from sql.
|
||||
*
|
||||
* @param object $statement
|
||||
* @param int $recPerPage
|
||||
* @param int $pageID
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function prepareSqlPager($statement, $recPerPage, $pageID)
|
||||
{
|
||||
if(!$statement->limit)
|
||||
{
|
||||
$statement->limit = new stdclass();
|
||||
}
|
||||
$statement->limit->offset = $recPerPage * ($pageID - 1);
|
||||
$statement->limit->rowCount = $recPerPage;
|
||||
|
||||
$statement->options->options[] = 'SQL_CALC_FOUND_ROWS';
|
||||
|
||||
$limitSql = $statement->build();
|
||||
|
||||
return $limitSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare columns setting from sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param object $statement
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function prepareColumns($sql, $statement)
|
||||
{
|
||||
$this->loadModel('chart');
|
||||
$this->loadModel('dataview');
|
||||
|
||||
// TODO 需要替换成实际图表或者透视表的driver。
|
||||
$driver = 'mysql';
|
||||
|
||||
$sqlColumns = $this->getColumns($sql, $driver);
|
||||
$columnTypes = $this->getColumnsType($sql, $driver, $sqlColumns);
|
||||
$columnFields = array();
|
||||
foreach($columnTypes as $column => $type) $columnFields[$column] = $column;
|
||||
|
||||
$tableAndFields = $this->getTableAndFields($sql);
|
||||
$tables = $tableAndFields['tables'];
|
||||
$fields = $tableAndFields['fields'];
|
||||
|
||||
$moduleNames = array();
|
||||
$aliasNames = array();
|
||||
if($tables)
|
||||
{
|
||||
$moduleNames = $this->dataview->getModuleNames($tables);
|
||||
$aliasNames = $this->dataview->getAliasNames($statement, $moduleNames);
|
||||
}
|
||||
|
||||
list($fields, $objects) = $this->dataview->mergeFields($columnFields, $fields, $moduleNames, $aliasNames);
|
||||
|
||||
$columns = array();
|
||||
foreach($fields as $field => $name)
|
||||
{
|
||||
$columns[$field] = array('name' => $name, 'field' => $field, 'type' => $columnTypes->$field, 'object' => $objects[$field]);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param int $recPerPage
|
||||
* @param int $pageID
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function query($sql, $recPerPage = 100, $pageID = 1)
|
||||
{
|
||||
$queryResult = new stdclass();
|
||||
$queryResult->error = false;
|
||||
$queryResult->errorMsg = '';
|
||||
$queryResult->cols = array();
|
||||
$queryResult->rows = array();
|
||||
$queryResult->sql = '';
|
||||
$queryResult->fieldSettings = array();
|
||||
|
||||
$statement = $this->sql2Statement($sql);
|
||||
if(is_string($statement))
|
||||
{
|
||||
$queryResult->error = true;
|
||||
$queryResult->errorMsg = $statement;
|
||||
|
||||
return $queryResult;
|
||||
}
|
||||
|
||||
$checked = $this->validateSql($sql);
|
||||
if($checked !== true)
|
||||
{
|
||||
$queryResult->error = true;
|
||||
$queryResult->errorMsg = $checked;
|
||||
|
||||
return $queryResult;
|
||||
}
|
||||
|
||||
$limitSql = $this->prepareSqlPager($statement, $recPerPage, $pageID);
|
||||
|
||||
try
|
||||
{
|
||||
$queryResult->rows = $this->dbh->query($limitSql)->fetchAll();
|
||||
$queryResult->rowsCount = $this->dbh->query("SELECT FOUND_ROWS() as count")->fetch()->count;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$queryResult->error = true;
|
||||
$queryResult->errorMsg = $e;
|
||||
|
||||
return $queryResult;
|
||||
}
|
||||
|
||||
$columns = $this->prepareColumns($limitSql, $statement);
|
||||
$clientLang = $this->app->getClientLang();
|
||||
|
||||
$fieldSettings = array();
|
||||
foreach($columns as $field => $settings)
|
||||
{
|
||||
$title = $settings['name'];
|
||||
|
||||
if(!isset($settings[$clientLang]) || empty($settings[$clientLang])) $settings[$clientLang] = $title;
|
||||
$fieldSettings[$field] = $settings;
|
||||
}
|
||||
|
||||
$queryResult->cols = $this->buildQueryResultTableColumns($fieldSettings);
|
||||
$queryResult->fieldSettings = $fieldSettings;
|
||||
$queryResult->sql = $limitSql;
|
||||
|
||||
return $queryResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build table columns from query result.
|
||||
*
|
||||
* @param array $fieldSettings
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function buildQueryResultTableColumns($fieldSettings)
|
||||
{
|
||||
$cols = array();
|
||||
$clientLang = $this->app->getClientLang();
|
||||
foreach($fieldSettings as $field => $settings)
|
||||
{
|
||||
$settings = (array)$settings;
|
||||
$title = $settings[$clientLang];
|
||||
$type = $settings['type'];
|
||||
$cols[] = array('name' => $field, 'title' => $title, 'sortType' => false);
|
||||
}
|
||||
|
||||
return $cols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare field objects.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function prepareFieldObjects()
|
||||
{
|
||||
$this->loadModel('dataview');
|
||||
$options = array();
|
||||
foreach($this->lang->dataview->objects as $table => $name)
|
||||
{
|
||||
$fields = $this->dataview->getTypeOptions($table);
|
||||
$options[] = array('text' => $name, 'value' => $table, 'fields' => $fields);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare field setting form data.
|
||||
*
|
||||
* @param object $settings
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function prepareFieldSettingFormData($settings)
|
||||
{
|
||||
$formData = array();
|
||||
foreach((array)$settings as $key => $setting)
|
||||
{
|
||||
$setting = (array)$setting;
|
||||
$setting['key'] = $key;
|
||||
$formData[] = $setting;
|
||||
}
|
||||
|
||||
return $formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert json string to array.
|
||||
*
|
||||
* @param string|object|array $json
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function json2Array(string|object|array $json): array
|
||||
{
|
||||
if(empty($json)) return array();
|
||||
if(is_string($json)) return json_decode($json, true);
|
||||
if(is_object($json)) return json_decode(json_encode($json), true);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型和模块ID获取正确的模块ID。
|
||||
* Get correct group id with type.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
class biTest
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
global $tester;
|
||||
$this->objectModel = $tester->loadModel('bi');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse sql test
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function parseSqlTest($sql)
|
||||
{
|
||||
$columns = $this->objectModel->parseSql($sql);
|
||||
|
||||
$result = array();
|
||||
foreach($columns as $field => $column)
|
||||
{
|
||||
if(empty($column['table'])) continue;
|
||||
|
||||
$result[$field] = "{$column['table']['originTable']}=>{$column['table']['column']}";
|
||||
}
|
||||
|
||||
arsort($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
include dirname(__FILE__, 2) . '/lib/bi.unittest.class.php';
|
||||
|
||||
/**
|
||||
|
||||
title=biModel->parseSql();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 测试第1条sql
|
||||
- 属性id @zt_product=>id
|
||||
- 属性name @zt_product=>name
|
||||
- 属性bugID @zt_bug=>id
|
||||
- 属性type @zt_bug=>type
|
||||
- 属性fixedBugs @zt_product=>fixedBugs
|
||||
- 属性caseTitle @zt_case=>title
|
||||
- 测试第2条sql
|
||||
- 属性id @zt_task=>id
|
||||
- 属性name @zt_task=>name
|
||||
- 属性consumed @zt_task=>consumed
|
||||
- 测试第3条sql
|
||||
- 属性id @zt_story=>id
|
||||
- 属性estimate @zt_story=>estimate
|
||||
- 测试第4条sql
|
||||
- 属性id @zt_story=>id
|
||||
- 属性hour @zt_story=>estimate
|
||||
- 测试第5条sql
|
||||
- 属性name @zt_project=>name
|
||||
- 属性PM @zt_project=>PM
|
||||
- 属性begin @zt_project=>begin
|
||||
- 属性realBegan @zt_project=>realBegan
|
||||
- 属性realname @zt_user=>realname
|
||||
|
||||
*/
|
||||
|
||||
$sqls = array();
|
||||
|
||||
$sqls[0] = <<<EOT
|
||||
select t1.id,t1.name,t2.id as bugID,t2.type, fixedBugs, caseTitle
|
||||
from zt_product as t1
|
||||
left join zt_bug as t2 on t1.id=t2.product and t2.deleted = '0'
|
||||
left join zt_project as t3 on t1.program=t3.id
|
||||
left join (select id, title as caseTitle from zt_case where deleted = '0') as t4 on t2.case = t4.id
|
||||
where t1.deleted='0' and t2.deleted='0'
|
||||
order by t3.`order` asc, t1.line desc, t1.`order` asc
|
||||
EOT;
|
||||
|
||||
$sqls[1] = <<<EOT
|
||||
select id,name,consumed from zt_task
|
||||
EOT;
|
||||
|
||||
$sqls[2] = <<<EOT
|
||||
SELECT DISTINCT id,estimate
|
||||
FROM zt_story t1
|
||||
WHERE 1 = (
|
||||
SELECT COUNT(DISTINCT id,estimate)
|
||||
FROM zt_story t2
|
||||
WHERE t2.estimate> t1.estimate
|
||||
)
|
||||
EOT;
|
||||
|
||||
$sqls[3] = <<<EOT
|
||||
SELECT DISTINCT id,estimate hour
|
||||
FROM zt_story t1
|
||||
WHERE 1 = (
|
||||
SELECT COUNT(DISTINCT id,estimate)
|
||||
FROM zt_story t2
|
||||
WHERE t2.estimate> t1.estimate
|
||||
)
|
||||
EOT;
|
||||
|
||||
$sqls[4] = <<<EOT
|
||||
SELECT
|
||||
t1.name,
|
||||
t1.PM,
|
||||
t1.begin,
|
||||
t1.realBegan,
|
||||
t3.realname
|
||||
FROM
|
||||
zt_project AS t1
|
||||
LEFT JOIN
|
||||
zt_team AS t2 ON t1.id = t2.root AND t2.type = 'execution'
|
||||
LEFT JOIN
|
||||
zt_user AS t3 ON t2.account = t3.account
|
||||
WHERE t1.name LIKE '%zentaopms%'
|
||||
AND t1.realBegan > '2022-01-01'
|
||||
order by t1.id
|
||||
EOT;
|
||||
|
||||
$bi=new biTest();
|
||||
r($bi->parseSqlTest($sqls[0])) && p('id,name,bugID,type,fixedBugs,caseTitle') && e('zt_product=>id,zt_product=>name,zt_bug=>id,zt_bug=>type,zt_product=>fixedBugs,zt_case=>title'); // 测试第1条sql
|
||||
r($bi->parseSqlTest($sqls[1])) && p('id,name,consumed') && e('zt_task=>id,zt_task=>name,zt_task=>consumed'); // 测试第2条sql
|
||||
r($bi->parseSqlTest($sqls[2])) && p('id,estimate') && e('zt_story=>id,zt_story=>estimate'); // 测试第3条sql
|
||||
r($bi->parseSqlTest($sqls[3])) && p('id,hour') && e('zt_story=>id,zt_story=>estimate'); // 测试第4条sql
|
||||
r($bi->parseSqlTest($sqls[4])) && p('name,PM,begin,realBegan,realname') && e('zt_project=>name,zt_project=>PM,zt_project=>begin,zt_project=>realBegan,zt_user=>realname'); // 测试第5条sql
|
||||
@@ -11,6 +11,12 @@
|
||||
*/
|
||||
class chart extends control
|
||||
{
|
||||
/**
|
||||
* __construct method.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $moduleName = '', string $methodName = '', string $appName = '')
|
||||
{
|
||||
parent::__construct($moduleName, $methodName, $appName);
|
||||
|
||||
+22
-62
@@ -200,16 +200,17 @@ class chartModel extends model
|
||||
{
|
||||
$settings = current($chart->settings);
|
||||
$type = $settings['type'];
|
||||
$driver = $chart->driver;
|
||||
$options = array();
|
||||
|
||||
$filterFormat = $this->getFilterFormat($chart->filters);
|
||||
|
||||
if($type == 'pie') $options = $this->genPie($chart->fieldSettings, $settings, $chart->sql, $filterFormat);
|
||||
if($type == 'radar') $options = $this->genRadar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $chart->langs);
|
||||
if($type == 'line') $options = $this->genLineChart($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $chart->langs);
|
||||
if($type == 'cluBarX' || $type == 'cluBarY') $options = $this->genCluBar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, '', $chart->langs);
|
||||
if($type == 'stackedBar' || $type == 'stackedBarY') $options = $this->genCluBar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, 'total', $chart->langs);
|
||||
if($type == 'waterpolo') $options = $this->bi->genWaterpolo($chart->fieldSettings, $settings, $chart->sql, $filterFormat);
|
||||
if($type == 'pie') $options = $this->genPie($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $driver);
|
||||
if($type == 'radar') $options = $this->genRadar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $chart->langs, $driver);
|
||||
if($type == 'line') $options = $this->genLineChart($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $chart->langs, $driver);
|
||||
if($type == 'cluBarX' || $type == 'cluBarY') $options = $this->genCluBar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, '', $chart->langs, $driver);
|
||||
if($type == 'stackedBar' || $type == 'stackedBarY') $options = $this->genCluBar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, 'total', $chart->langs, $driver);
|
||||
if($type == 'waterpolo') $options = $this->bi->genWaterpolo($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $driver);
|
||||
|
||||
if(empty($options)) return array();
|
||||
|
||||
@@ -280,9 +281,9 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function genRadar(array $fields, array $settings, string $defaultSql, array $filters, array $langs = array()): array
|
||||
public function genRadar(array $fields, array $settings, string $defaultSql, array $filters, array $langs = array(), $driver = 'mysql'): array
|
||||
{
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters);
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters, $driver);
|
||||
|
||||
$yDatas = array();
|
||||
$max = 0;
|
||||
@@ -332,14 +333,14 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function genPie(array $fields, array $settings, string $sql, array $filters): array
|
||||
public function genPie(array $fields, array $settings, string $sql, array $filters, string $driver = 'mysql'): array
|
||||
{
|
||||
$group = isset($settings['group'][0]['field']) ? $settings['group'][0]['field'] : '';
|
||||
$date = isset($settings['group'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['group'][0]['group']) : '';
|
||||
$metric = isset($settings['metric'][0]['field']) ? $settings['metric'][0]['field'] : '';
|
||||
$agg = isset($settings['metric'][0]['valOrAgg']) ? $settings['metric'][0]['valOrAgg'] : '';
|
||||
|
||||
$rows = $this->chartTao->getRows(str_replace(';', '', $sql), $filters, $date, $group, $metric, $agg);
|
||||
$rows = $this->chartTao->getRows(str_replace(';', '', $sql), $filters, $date, $group, $metric, $agg, $driver);
|
||||
$stat = $this->chartTao->processRows($rows, $date, $group, $metric);
|
||||
if(empty($date)) arsort($stat);
|
||||
|
||||
@@ -390,9 +391,9 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function genLineChart(array $fields, array $settings, string $defaultSql, array $filters, array $langs = array()): array
|
||||
public function genLineChart(array $fields, array $settings, string $defaultSql, array $filters, array $langs = array(), string $driver = 'mysql'): array
|
||||
{
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters);
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters, $driver);
|
||||
|
||||
$fieldType = $fields[$settings['xaxis'][0]['field']]['type'];
|
||||
if($fieldType == 'date') sort($xLabels);
|
||||
@@ -438,9 +439,9 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function genCluBar(array $fields, array $settings, string $defaultSql, array $filters, string $stack = '', array $langs = array()): array
|
||||
public function genCluBar(array $fields, array $settings, string $defaultSql, array $filters, string $stack = '', array $langs = array(), $driver = 'mysql'): array
|
||||
{
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters);
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters, $driver);
|
||||
|
||||
$yDatas = array();
|
||||
foreach($yStats as $yStat)
|
||||
@@ -508,7 +509,7 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getMultiData(array $settings, string $defaultSql, array $filters, bool $sort = false): array
|
||||
public function getMultiData(array $settings, string $defaultSql, array $filters, string $driver, bool $sort = false): array
|
||||
{
|
||||
$group = isset($settings['xaxis'][0]['field']) ? $settings['xaxis'][0]['field'] : '';
|
||||
$date = isset($settings['xaxis'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['xaxis'][0]['group']) : '';
|
||||
@@ -552,7 +553,7 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function genWaterpolo(array $settings, string $sql, array $filters)
|
||||
public function genWaterpolo(array $settings, string $sql, array $filters, string $driver = 'mysql')
|
||||
{
|
||||
$operate = "{$settings['calc']}({$settings['goal']})";
|
||||
$sql = "select $operate count from ($sql) tt ";
|
||||
@@ -583,8 +584,8 @@ class chartModel extends model
|
||||
if($moleculeWheres) $moleculeSQL .= 'where ' . implode(' and ', $moleculeWheres);
|
||||
if($denominatorWheres) $denominatorSQL .= 'where ' . implode(' and ', $denominatorWheres);
|
||||
|
||||
$molecule = $this->dao->query($moleculeSQL)->fetch();
|
||||
$denominator = $this->dao->query($denominatorSQL)->fetch();
|
||||
$molecule = $this->bi->queryWithDriver($driver, $moleculeSQL, false);
|
||||
$denominator = $this->bi->queryWithDriver($driver, $denominatorSQL, false);
|
||||
|
||||
$percent = $denominator->count ? round($molecule->count / $denominator->count, 4) : 0;
|
||||
|
||||
@@ -607,7 +608,7 @@ class chartModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getSysOptions(string $type, string $object = '', string $field = '', string $sql = '', string $saveAs = ''): array
|
||||
public function getSysOptions(string $type, string $object = '', string $field = '', string $sql = '', string $saveAs = '', $driver = 'mysql'): array
|
||||
{
|
||||
if(in_array($type, array('user', 'product', 'project', 'execution', 'dept'))) return $this->bi->getScopeOptions($type);
|
||||
if(!$field) return array();
|
||||
@@ -627,14 +628,14 @@ class chartModel extends model
|
||||
{
|
||||
$keyField = $field;
|
||||
$valueField = $saveAs ? $saveAs : $field;
|
||||
$options = $this->bi->getOptionsFromSql($sql, $keyField, $valueField);
|
||||
$options = $this->bi->getOptionsFromSql($sql, $driver, $keyField, $valueField);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if($sql and $saveAs and in_array($type, array('user', 'product', 'project', 'execution', 'dept', 'option', 'object')))
|
||||
{
|
||||
$options = $this->bi->getOptionsFromSql($sql, $field, $saveAs);
|
||||
$options = $this->bi->getOptionsFromSql($sql, $driver, $field, $saveAs);
|
||||
}
|
||||
|
||||
return array_filter($options);
|
||||
@@ -738,47 +739,6 @@ class chartModel extends model
|
||||
return $filterFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sql中的表、字段。
|
||||
* Get tables and fields form sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return array|false
|
||||
*/
|
||||
public function getTables(string $sql): array|false
|
||||
{
|
||||
$processedSql = trim($sql, ';');
|
||||
$processedSql = str_replace(array("\r\n", "\n"), ' ', $processedSql);
|
||||
$processedSql = str_replace('`', '', $processedSql);
|
||||
preg_match_all('/^select (.+) from (.+)$/i', $processedSql, $selectAndFrom);
|
||||
if(empty($selectAndFrom[2][0])) return false;
|
||||
|
||||
$tableSql = $fromSql = $selectAndFrom[2][0];
|
||||
if(stripos($fromSql, 'where') !== false) $tableSql = trim(substr($fromSql, 0, stripos($fromSql, 'where')));
|
||||
if(stripos($fromSql, 'limit') !== false) $tableSql = trim(substr($fromSql, 0, stripos($fromSql, 'limit')));
|
||||
if(stripos($fromSql, 'having') !== false) $tableSql = trim(substr($fromSql, 0, stripos($fromSql, 'having')));
|
||||
if(stripos($fromSql, 'group by') !== false) $tableSql = trim(substr($fromSql, 0, stripos($fromSql, 'group by')));
|
||||
|
||||
/* Remove such as "left join|right join|join", "on (t1.id=t2.id)", result like t1, t2 as t3. */
|
||||
$tableSql .= ' ';
|
||||
if(stripos($tableSql, 'join') !== false) $tableSql = preg_replace(array('/join\s+([A-Z]+_\w+ .*)on/Ui', '/,\s*on\s+[^,]+/i'), array(',$1,on', ''), $tableSql);
|
||||
$tableSql = str_replace(array('left', 'right'), '', $tableSql);
|
||||
|
||||
/* Match t2 as t3 */
|
||||
preg_match_all('/(\w+) +as +(\w+)/i', $tableSql, $matchOut);
|
||||
|
||||
$tableSql = preg_replace('/as +\w+/i', ' ', $tableSql);
|
||||
$tableSql = trim(str_replace(array('(', ')', ','), ' ', $tableSql));
|
||||
$tableSql = preg_replace('/ +/', ' ', $tableSql);
|
||||
|
||||
$tables = explode(' ', $tableSql);
|
||||
|
||||
list($fields, $sql) = $this->chartTao->getFieldsBySql($selectAndFrom[1][0], $sql, $matchOut);
|
||||
|
||||
return array('sql' => $sql, 'tables' => $tables, 'fields' => $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在sql中将变量解析为空字符串。
|
||||
* Parse variables to null string in sql.
|
||||
|
||||
+3
-54
@@ -15,7 +15,7 @@ class chartTao extends chartModel
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getRows(string $defaultSql, array $filters, string $date, string $group, string $metric, string $agg): array
|
||||
protected function getRows(string $defaultSql, array $filters, string $date, string $group, string $metric, string $agg, string $driver): array
|
||||
{
|
||||
$groupSql = $groupBySql = "tt.`$group`";
|
||||
if(!empty($date))
|
||||
@@ -47,7 +47,8 @@ class chartTao extends chartModel
|
||||
}
|
||||
$sql .= " group by $groupBySql";
|
||||
|
||||
return $this->dao->query($sql)->fetchAll();
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
return $dbh->query($sql)->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,56 +122,4 @@ class chartTao extends chartModel
|
||||
|
||||
return $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在sql中提取字段。
|
||||
* Switch field name.
|
||||
*
|
||||
* @param string $selectSql
|
||||
* @param string $sql
|
||||
* @param array $out
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldsBySql(string $selectSql, string $sql, array $out): array
|
||||
{
|
||||
$fields = explode(',', $selectSql);
|
||||
foreach($fields as $i => $field)
|
||||
{
|
||||
if($field) $asField = '';
|
||||
if(strrpos($field, ' as ') !== false) list($field, $asField) = explode(' as ', $field);
|
||||
if(strrpos($field, ' AS ') !== false) list($field, $asField) = explode(' AS ', $field);
|
||||
|
||||
$field = trim($field);
|
||||
$asField = trim($asField);
|
||||
$fieldName = $field;
|
||||
if(strrpos($field, '.') !== false)
|
||||
{
|
||||
$table = substr($field, 0, strrpos($field, '.'));
|
||||
$fieldName = substr($field, strrpos($field, '.') + 1);
|
||||
if(!empty($out[0]) and in_array($table, $out[2]))
|
||||
{
|
||||
$realTable = $out[1][array_search($table, $out[2])];
|
||||
$tableFieldName = str_replace($table . '.', $realTable . '.', $field);
|
||||
|
||||
if(isset($fields[$fieldName]) && !$asField)
|
||||
{
|
||||
$fieldName = str_replace('.', '', $field);
|
||||
|
||||
$sql = preg_replace(array("/$field/", "/`$field`/"), array("$field AS $fieldName", "`$field` AS $fieldName"), $sql, 1);
|
||||
|
||||
$field = $tableFieldName;
|
||||
}
|
||||
}
|
||||
|
||||
if($fieldName == '*') $fieldName = $field;
|
||||
}
|
||||
|
||||
$fieldName = $asField ? $asField : $fieldName;
|
||||
|
||||
$fields[$fieldName] = $field;
|
||||
unset($fields[$i]);
|
||||
}
|
||||
return array($fields, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,41 +12,3 @@ $config->dataview->groupChild['company'] = array('user' => 'user,usercontact,use
|
||||
|
||||
$config->dataview->multipleMappingFields = array();
|
||||
$config->dataview->multipleMappingFields[] = 'testcase-stage';
|
||||
|
||||
$config->dataview->columnTypes = new stdclass();
|
||||
$config->dataview->columnTypes->TINY = 'number';
|
||||
$config->dataview->columnTypes->SHORT = 'number';
|
||||
$config->dataview->columnTypes->LONG = 'number';
|
||||
$config->dataview->columnTypes->FLOAT = 'number';
|
||||
$config->dataview->columnTypes->DOUBLE = 'number';
|
||||
$config->dataview->columnTypes->TIMESTAMP = 'string';
|
||||
$config->dataview->columnTypes->LONGLONG = 'string';
|
||||
$config->dataview->columnTypes->INT24 = 'number';
|
||||
$config->dataview->columnTypes->DATE = 'date';
|
||||
$config->dataview->columnTypes->TIME = 'string';
|
||||
$config->dataview->columnTypes->DATETIME = 'date';
|
||||
$config->dataview->columnTypes->YEAR = 'date';
|
||||
$config->dataview->columnTypes->ENUM = 'string';
|
||||
$config->dataview->columnTypes->SET = 'string';
|
||||
$config->dataview->columnTypes->TINYBLOB = 'string';
|
||||
$config->dataview->columnTypes->MEDIUMBLOB = 'string';
|
||||
$config->dataview->columnTypes->LONG_BLOB = 'string';
|
||||
$config->dataview->columnTypes->BLOB = 'string';
|
||||
$config->dataview->columnTypes->VAR_STRING = 'string';
|
||||
$config->dataview->columnTypes->STRING = 'string';
|
||||
$config->dataview->columnTypes->NULL = 'null';
|
||||
$config->dataview->columnTypes->NEWDATE = 'date';
|
||||
$config->dataview->columnTypes->INTERVAL = 'string';
|
||||
$config->dataview->columnTypes->GEOMETRY = 'string';
|
||||
$config->dataview->columnTypes->NEWDECIMAL = 'number';
|
||||
|
||||
/* Dameng native_type. */
|
||||
$config->dataview->columnTypes->int = 'number';
|
||||
$config->dataview->columnTypes->varchar = 'string';
|
||||
$config->dataview->columnTypes->text = 'string';
|
||||
$config->dataview->columnTypes->timestamp = 'string';
|
||||
$config->dataview->columnTypes->date = 'date';
|
||||
$config->dataview->columnTypes->time = 'string';
|
||||
$config->dataview->columnTypes->double = 'number';
|
||||
$config->dataview->columnTypes->number = 'number';
|
||||
$config->dataview->columnTypes->bigint = 'number';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
global $lang;
|
||||
$config->dataview->schema = new stdclass();
|
||||
$config->dataview->schema->dtable = new stdclass();
|
||||
$config->dataview->schema->dtable->fieldList = array();
|
||||
$config->dataview->schema->dtable->fieldList['id']['name'] = 'id';
|
||||
$config->dataview->schema->dtable->fieldList['id']['type'] = 'id';
|
||||
|
||||
$config->dataview->schema->dtable->fieldList['name']['name'] = 'name';
|
||||
$config->dataview->schema->dtable->fieldList['name']['type'] = 'title';
|
||||
|
||||
$config->dataview->schema->dtable->fieldList['desc']['name'] = 'desc';
|
||||
$config->dataview->schema->dtable->fieldList['desc']['type'] = 'desc';
|
||||
|
||||
$config->dataview->schema->dtable->fieldList['type']['name'] = 'type';
|
||||
$config->dataview->schema->dtable->fieldList['type']['type'] = 'category';
|
||||
|
||||
$config->dataview->schema->dtable->fieldList['length']['name'] = 'length';
|
||||
$config->dataview->schema->dtable->fieldList['length']['title'] = $lang->dataview->length;
|
||||
$config->dataview->schema->dtable->fieldList['length']['type'] = 'number';
|
||||
|
||||
$config->dataview->schema->dtable->fieldList['null']['name'] = 'null';
|
||||
$config->dataview->schema->dtable->fieldList['null']['type'] = 'text';
|
||||
@@ -33,7 +33,7 @@ function query(callback) {
|
||||
|
||||
$('.query').addClass('disabled');
|
||||
$('#querying').removeClass('hidden');
|
||||
$.post(createLink('dataview', 'ajaxQuery'), {sql: $('#sql').val(), filters: filters, recPerPage: DataStorage.recPerPage, pageID: DataStorage.pageID}, function(resp)
|
||||
$.post(createLink('dataview', 'ajaxQuery'), {sql: $('#sql').val(), driver: DataStorage.driver, filters: filters, recPerPage: DataStorage.recPerPage, pageID: DataStorage.pageID}, function(resp)
|
||||
{
|
||||
resp = JSON.parse(resp);
|
||||
$('.query').removeClass('disabled');
|
||||
|
||||
@@ -11,6 +11,7 @@ $lang->dataview->code = 'Code';
|
||||
$lang->dataview->group = 'Group';
|
||||
$lang->dataview->view = 'View Name';
|
||||
$lang->dataview->desc = 'Description';
|
||||
$lang->dataview->length = 'length';
|
||||
$lang->dataview->data = 'Data';
|
||||
$lang->dataview->schema = 'Schema';
|
||||
$lang->dataview->details = 'Details';
|
||||
|
||||
@@ -11,6 +11,7 @@ $lang->dataview->code = 'Code';
|
||||
$lang->dataview->group = 'Group';
|
||||
$lang->dataview->view = 'View Name';
|
||||
$lang->dataview->desc = 'Description';
|
||||
$lang->dataview->length = 'length';
|
||||
$lang->dataview->data = 'Data';
|
||||
$lang->dataview->schema = 'Schema';
|
||||
$lang->dataview->details = 'Details';
|
||||
|
||||
@@ -11,6 +11,7 @@ $lang->dataview->code = 'Code';
|
||||
$lang->dataview->group = 'Group';
|
||||
$lang->dataview->view = 'View Name';
|
||||
$lang->dataview->desc = 'Description';
|
||||
$lang->dataview->length = 'length';
|
||||
$lang->dataview->data = 'Data';
|
||||
$lang->dataview->schema = 'Schema';
|
||||
$lang->dataview->details = 'Details';
|
||||
|
||||
@@ -11,6 +11,7 @@ $lang->dataview->code = '代号';
|
||||
$lang->dataview->group = '所属分组';
|
||||
$lang->dataview->view = '视图名称';
|
||||
$lang->dataview->desc = '描述';
|
||||
$lang->dataview->length = '长度';
|
||||
$lang->dataview->data = '数据';
|
||||
$lang->dataview->schema = '字段';
|
||||
$lang->dataview->details = '详情';
|
||||
|
||||
+11
-38
@@ -22,7 +22,7 @@ class dataviewModel extends model
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->loadBIDAO();
|
||||
$this->loadModel('bi');
|
||||
}
|
||||
|
||||
|
||||
@@ -229,61 +229,34 @@ class dataviewModel extends model
|
||||
return array($mergeFields, $relatedObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表的字段类型。
|
||||
* Get table data.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function getColumns(string $sql): object
|
||||
{
|
||||
$columns = $this->dao->getColumns($sql);
|
||||
|
||||
$columnTypes = new stdclass();
|
||||
foreach($columns as $column)
|
||||
{
|
||||
$field = $column['name'];
|
||||
$nativeType = $column['native_type'];
|
||||
$type = $this->config->dataview->columnTypes->$nativeType;
|
||||
|
||||
if(isset($columnTypes->$field)) $field = $column['table'] . $field;
|
||||
$columnTypes->$field = $type;
|
||||
}
|
||||
|
||||
return $columnTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查查询结果的唯一性。
|
||||
* Check that the column of an sql query is unique.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $driverName mysql|duckdb
|
||||
* @param bool $repeat
|
||||
* @param array $columns
|
||||
* @access public
|
||||
* @return bool|array
|
||||
*/
|
||||
public function checkUniColumn(string $sql, bool $repeat = false): bool|array
|
||||
public function checkUniColumn(string $sql, string $driverName = 'mysql', bool $repeat = false, $columns = array()): bool|array
|
||||
{
|
||||
$columns = $this->dao->getColumns($sql);
|
||||
if(empty($columns)) $columns = $this->bi->getColumns($sql, $driverName);
|
||||
|
||||
$isUnique = true;
|
||||
$columnList = array();
|
||||
$repeatColumn = array();
|
||||
foreach($columns as $column)
|
||||
$repeatFields = array();
|
||||
$fieldCounts = array_count_values(array_column($columns, 'name'));
|
||||
foreach($fieldCounts as $field => $value)
|
||||
{
|
||||
$field = $column['name'];
|
||||
if(isset($columnTypes[$field]))
|
||||
if($value > 1)
|
||||
{
|
||||
$isUnique = false;
|
||||
$repeatColumn[$field] = $field;
|
||||
$repeatFields[$field] = $field;
|
||||
}
|
||||
|
||||
$columnTypes[$field] = $field;
|
||||
}
|
||||
|
||||
if($repeat) return array($isUnique, $repeatColumn);
|
||||
if($repeat) return array($isUnique, $repeatFields);
|
||||
return $isUnique;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,19 +112,17 @@ class pivot extends control
|
||||
$settings['filterType'] = $filterType;
|
||||
|
||||
$sql = str_replace(';', '', "$post->sql");
|
||||
$driver = $pivot->driver;
|
||||
$fields = $post->fieldSettings;
|
||||
$langs = isset($post->langs) ? (is_array($post->langs) ? $post->langs : json_decode($post->langs, true)) : array();
|
||||
|
||||
$processSqlData = $this->loadModel('chart')->getTables($sql);
|
||||
$sql = $processSqlData['sql'];
|
||||
|
||||
if(isset($settings['summary']) and $settings['summary'] == 'notuse')
|
||||
{
|
||||
list($data, $configs) = $this->pivot->genOriginSheet($fields, $settings, $sql, $filterFormat, $langs);
|
||||
list($data, $configs) = $this->pivot->genOriginSheet($fields, $settings, $sql, $filterFormat, $langs, $driver);
|
||||
}
|
||||
else
|
||||
{
|
||||
list($data, $configs) = $this->pivot->genSheet($fields, $settings, $sql, $filterFormat, $langs);
|
||||
list($data, $configs) = $this->pivot->genSheet($fields, $settings, $sql, $filterFormat, $langs, $driver);
|
||||
}
|
||||
|
||||
$this->pivot->buildPivotTable($data, $configs, $page);
|
||||
|
||||
+24
-19
@@ -188,6 +188,7 @@ class pivotModel extends model
|
||||
*/
|
||||
public function processFieldSettings(object $pivot): void
|
||||
{
|
||||
$this->loadModel('dataview');
|
||||
$fieldSettings = $pivot->fieldSettings ?? $this->getFieldsFromPivot($pivot, 'fields', array(), true);
|
||||
if(empty($fieldSettings)) return;
|
||||
|
||||
@@ -202,11 +203,11 @@ class pivotModel extends model
|
||||
$stmt = $this->dbh->query($querySQL);
|
||||
if(!$stmt) return;
|
||||
|
||||
$columns = $this->loadModel('dataview')->getColumns($querySQL);
|
||||
$columns = $this->bi->getColumnsType($querySQL);
|
||||
$columnFields = array();
|
||||
foreach(array_keys(get_object_vars($columns)) as $type) $columnFields[$type] = $type;
|
||||
|
||||
extract($this->chart->getTables($querySQL));
|
||||
extract($this->bi->getTableAndFields($querySQL));
|
||||
|
||||
/* 获取field的键值对以及相关联的对象。 */
|
||||
/* Get field key value pairs and related objects. */
|
||||
@@ -800,7 +801,7 @@ class pivotModel extends model
|
||||
$connectSQL .= " where $whereStr";
|
||||
}
|
||||
|
||||
$sql = "select * from ($sql) tt" . $connectSQL;
|
||||
$sql = "select * from ( $sql ) tt" . $connectSQL;
|
||||
|
||||
return $sql;
|
||||
}
|
||||
@@ -814,11 +815,11 @@ class pivotModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function mapRecordValueWithFieldOptions(array $records, array $fields, string $sql): array
|
||||
public function mapRecordValueWithFieldOptions(array $records, array $fields, string $sql, string $driver): array
|
||||
{
|
||||
$this->app->loadConfig('dataview');
|
||||
$records = json_decode(json_encode($records), true);
|
||||
$fieldOptions = $this->getFieldsOptions($fields, $sql);
|
||||
$fieldOptions = $this->getFieldsOptions($fields, $sql, $driver);
|
||||
foreach($records as $index => $record)
|
||||
{
|
||||
foreach($record as $field => $value)
|
||||
@@ -1323,7 +1324,7 @@ class pivotModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function genSheet(array $fields, array $settings, string $sql, array $filters, array $langs = array()): array
|
||||
public function genSheet(array $fields, array $settings, string $sql, array $filters, array $langs = array(), string $driver = 'mysql'): array
|
||||
{
|
||||
$groups = $this->getGroupsFromSettings($settings);
|
||||
$cols = $this->generateTableCols($fields, $groups, $langs);
|
||||
@@ -1333,8 +1334,9 @@ class pivotModel extends model
|
||||
$sql = $this->trimSemicolon($sql);
|
||||
$sql = $this->appendWhereFilterToSql($sql, $filters);
|
||||
|
||||
$records = $this->dao->query($sql)->fetchAll();
|
||||
$records = $this->mapRecordValueWithFieldOptions($records, $fields, $sql);
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
$records = $dbh->query($sql)->fetchAll();
|
||||
$records = $this->mapRecordValueWithFieldOptions($records, $fields, $sql, $driver);
|
||||
|
||||
$showColTotal = zget($settings, 'columnTotal', 'noShow');
|
||||
|
||||
@@ -1350,7 +1352,7 @@ class pivotModel extends model
|
||||
$columnField = $columnSetting['field'];
|
||||
$columnSlice = zget($columnSetting, 'slice', 'noSlice');
|
||||
|
||||
$cols = $this->getTableHeader($records, $columnSetting, $fields, $cols, $sql, $langs, $columnShowOrigin);
|
||||
$cols = $this->getTableHeader($records, $columnSetting, $fields, $cols, $sql, $langs, $columnShowOrigin, $driver);
|
||||
|
||||
if($columnShowOrigin)
|
||||
{
|
||||
@@ -1470,7 +1472,7 @@ class pivotModel extends model
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function genOriginSheet($fields, $settings, $sql, $filters, $langs = array())
|
||||
public function genOriginSheet($fields, $settings, $sql, $filters, $langs = array(), $driver = 'mysql')
|
||||
{
|
||||
$sql = $this->initVarFilter($filters, $sql);
|
||||
|
||||
@@ -1500,7 +1502,9 @@ class pivotModel extends model
|
||||
$sql = $statement->build();
|
||||
|
||||
$columnSQL = "select * from ($sql) tt" . $connectSQL;
|
||||
$rows = $this->dao->query($columnSQL)->fetchAll();
|
||||
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
$rows = $dbh->query($columnSQL)->fetchAll();
|
||||
$rows = json_decode(json_encode($rows), true);
|
||||
|
||||
$cols = array();
|
||||
@@ -1847,7 +1851,7 @@ class pivotModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getTableHeader($columnRows, $column, $fields, $cols, $sql, $langs = array(), $showOrigin = false)
|
||||
public function getTableHeader($columnRows, $column, $fields, $cols, $sql, $langs = array(), $showOrigin = false, $driver = 'mysql')
|
||||
{
|
||||
$stat = zget($column, 'stat', '');
|
||||
$showMode = zget($column, 'showMode', 'default');
|
||||
@@ -1890,7 +1894,7 @@ class pivotModel extends model
|
||||
$sliceList = array();
|
||||
foreach($columnRows as $rows) $sliceList[$rows->{$slice}] = $rows->{$slice};
|
||||
|
||||
$optionList = $this->getSysOptions($fields[$slice]['type'], $fields[$slice]['object'], $fields[$slice]['field'], $sql);
|
||||
$optionList = $this->getSysOptions($fields[$slice]['type'], $fields[$slice]['object'], $fields[$slice]['field'], $sql, '', $driver);
|
||||
foreach($sliceList as $field)
|
||||
{
|
||||
$childCol = new stdclass();
|
||||
@@ -2200,7 +2204,7 @@ class pivotModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getSysOptions($type, $object = '', $field = '', $source = '', $saveAs = '')
|
||||
public function getSysOptions($type, $object = '', $field = '', $source = '', $saveAs = '', $driver = 'mysql')
|
||||
{
|
||||
if(in_array($type, array('user', 'product', 'project', 'execution', 'dept', 'project.status'))) return $this->bi->getScopeOptions($type);
|
||||
if(!$field) return array();
|
||||
@@ -2235,7 +2239,7 @@ class pivotModel extends model
|
||||
{
|
||||
$keyField = $field;
|
||||
$valueField = $saveAs ? $saveAs : $field;
|
||||
$options = $this->bi->getOptionsFromSql($source, $keyField, $valueField);
|
||||
$options = $this->bi->getOptionsFromSql($source, $driver, $keyField, $valueField);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -2243,7 +2247,7 @@ class pivotModel extends model
|
||||
|
||||
if(is_string($source) and $source and $saveAs and in_array($type, array('user', 'product', 'project', 'execution', 'dept', 'option', 'object')))
|
||||
{
|
||||
$options = $this->bi->getOptionsFromSql($source, $field, $saveAs);
|
||||
$options = $this->bi->getOptionsFromSql($source, $driver, $field, $saveAs);
|
||||
}
|
||||
|
||||
return array_filter($options);
|
||||
@@ -2317,11 +2321,12 @@ class pivotModel extends model
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getFieldsOptions(array $fieldSettings, string $sql): array
|
||||
public function getFieldsOptions(array $fieldSettings, string $sql, string $driver = 'mysql'): array
|
||||
{
|
||||
$options = array();
|
||||
|
||||
$sqlRecords = $this->dbh->query($sql)->fetchAll();
|
||||
$dbh = $this->app->loadDriver($driver);
|
||||
$sqlRecords = $dbh->query($sql)->fetchAll();
|
||||
|
||||
foreach($fieldSettings as $key => $fieldSetting)
|
||||
{
|
||||
@@ -2332,7 +2337,7 @@ class pivotModel extends model
|
||||
$source = $sql;
|
||||
if(in_array($type, array('string', 'number', 'date'))) $source = $sqlRecords;
|
||||
|
||||
$options[$key] = $this->getSysOptions($type, $object, $field, $source);
|
||||
$options[$key] = $this->getSysOptions($type, $object, $field, $source, '', $driver);
|
||||
}
|
||||
|
||||
return $options;
|
||||
|
||||
@@ -152,7 +152,8 @@ class pivotZen extends pivot
|
||||
*/
|
||||
public function show(int $groupID, int $pivotID): void
|
||||
{
|
||||
$pivot = $this->pivot->getByID($pivotID);
|
||||
$pivot = $this->pivot->getByID($pivotID);
|
||||
$driver = $pivot->driver;
|
||||
if(isset($_POST['filterValues']) and $_POST['filterValues'])
|
||||
{
|
||||
foreach($this->post->filterValues as $key => $value) $pivot->filters[$key]['default'] = $value;
|
||||
@@ -161,18 +162,16 @@ class pivotZen extends pivot
|
||||
|
||||
list($sql, $filterFormat) = $this->pivot->getFilterFormat($pivot->sql, $pivot->filters);
|
||||
|
||||
$tables = $this->loadModel('chart')->getTables($sql);
|
||||
$sql = $tables['sql'];
|
||||
$fields = json_decode(json_encode($pivot->fieldSettings), true);
|
||||
$langs = json_decode($pivot->langs, true) ?? array();
|
||||
|
||||
if(isset($pivot->settings['summary']) and $pivot->settings['summary'] == 'notuse')
|
||||
{
|
||||
list($data, $configs) = $this->pivot->genOriginSheet($fields, $pivot->settings, $sql, $filterFormat, $langs);
|
||||
list($data, $configs) = $this->pivot->genOriginSheet($fields, $pivot->settings, $sql, $filterFormat, $langs, $driver);
|
||||
}
|
||||
else
|
||||
{
|
||||
list($data, $configs) = $this->pivot->genSheet($fields, $pivot->settings, $sql, $filterFormat, $langs);
|
||||
list($data, $configs) = $this->pivot->genSheet($fields, $pivot->settings, $sql, $filterFormat, $langs, $driver);
|
||||
}
|
||||
|
||||
$this->view->pivotName = $pivot->name;
|
||||
|
||||
+16
-16
@@ -757,7 +757,7 @@ class screenModel extends model
|
||||
$settings = $settings[0];
|
||||
|
||||
$isSort = in_array($chart->id, $this->config->screen->annualRankingChart) ? true : false;
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $isSort);
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $chart->driver, $isSort);
|
||||
|
||||
$fields = json_decode($chart->fields, true);
|
||||
$dimensions = array($settings['xaxis'][0]['field']);
|
||||
@@ -809,7 +809,7 @@ class screenModel extends model
|
||||
$langs = json_decode($chart->langs, true);
|
||||
$settings = $settings[0];
|
||||
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters);
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $chart->driver);
|
||||
|
||||
$fields = json_decode($chart->fields, true);
|
||||
$dimensions = array($settings['xaxis'][0]['field']);
|
||||
@@ -868,7 +868,7 @@ class screenModel extends model
|
||||
$settings = json_decode($chart->settings, true);
|
||||
$settings = $settings[0];
|
||||
|
||||
$options = $this->loadModel('chart')->genPie(json_decode($chart->fields, true), $settings, $chart->sql, $filters);
|
||||
$options = $this->loadModel('chart')->genPie(json_decode($chart->fields, true), $settings, $chart->sql, $filters, $chart->driver);
|
||||
|
||||
if($settings['group'][0]['field'] == $settings['metric'][0]['field']) $settings['group'][0]['field'] = $settings['group'][0]['field'] . '1';
|
||||
$dimensions = array($settings['group'][0]['field'], $settings['metric'][0]['field']);
|
||||
@@ -915,7 +915,7 @@ class screenModel extends model
|
||||
$langs = json_decode($chart->langs, true);
|
||||
$settings = $settings[0];
|
||||
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters);
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->bi->getMultiData($settings, $chart->sql, $filters, $chart->driver);
|
||||
|
||||
$fields = json_decode($chart->fields, true);
|
||||
$radarIndicator = array();
|
||||
@@ -984,11 +984,11 @@ class screenModel extends model
|
||||
|
||||
if(isset($settings['summary']) and $settings['summary'] == 'notuse')
|
||||
{
|
||||
list($options, $config) = $this->loadModel('pivot')->genOriginSheet($fields, $settings, $chart->sql, $filters, $langs);
|
||||
list($options, $config) = $this->loadModel('pivot')->genOriginSheet($fields, $settings, $chart->sql, $filters, $langs, $chart->driver);
|
||||
}
|
||||
else
|
||||
{
|
||||
list($options, $config) = $this->loadModel('pivot')->genSheet($fields, $settings, $chart->sql, $filters, $langs);
|
||||
list($options, $config) = $this->loadModel('pivot')->genSheet($fields, $settings, $chart->sql, $filters, $langs, $chart->driver);
|
||||
}
|
||||
|
||||
$colspan = array();
|
||||
@@ -1617,7 +1617,7 @@ class screenModel extends model
|
||||
{
|
||||
$field = $settings->value->field;
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->buildDataset($chart->id, $sql);
|
||||
$results = $this->buildDataset($chart->id, $chart->driver, $sql);
|
||||
|
||||
if($settings->value->type === 'text')
|
||||
{
|
||||
@@ -1685,7 +1685,7 @@ class screenModel extends model
|
||||
$sourceData = array();
|
||||
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->dao->query($sql)->fetchAll();
|
||||
$results = $this->bi->queryWithDriver($chart->driver, $sql);
|
||||
foreach($results as $result)
|
||||
{
|
||||
$key = $settings->xaxis[0]->name;
|
||||
@@ -1744,7 +1744,7 @@ class screenModel extends model
|
||||
}
|
||||
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->buildDataset($chart->id, $sql);
|
||||
$results = $this->buildDataset($chart->id, $chart->driver, $sql);
|
||||
|
||||
foreach($results as $result)
|
||||
{
|
||||
@@ -1798,7 +1798,7 @@ class screenModel extends model
|
||||
$sourceData = array();
|
||||
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->dao->query($sql)->fetchAll();
|
||||
$results = $this->bi->queryWithDriver($chart->driver, $sql);
|
||||
|
||||
foreach($results as $result)
|
||||
{
|
||||
@@ -1869,7 +1869,7 @@ class screenModel extends model
|
||||
$sourceData = array();
|
||||
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->dao->query($sql)->fetchAll();
|
||||
$results = $this->bi->queryWithDriver($driver, $sql);
|
||||
$group = $settings->group[0]->field;
|
||||
|
||||
$groupCount = array();
|
||||
@@ -1944,7 +1944,7 @@ class screenModel extends model
|
||||
$sourceData = array();
|
||||
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->dao->query($sql)->fetchAll();
|
||||
$results = $this->bi->queryWithDriver($chart->driver, $sql);
|
||||
$group = $settings->group[0]->field;
|
||||
|
||||
$groupCount = array();
|
||||
@@ -2035,7 +2035,7 @@ class screenModel extends model
|
||||
else
|
||||
{
|
||||
$setting = json_decode($chart->settings, true)[0];
|
||||
$options = $this->bi->genWaterPolo(json_decode($chart->fields, true), $setting, $chart->sql, $filters);
|
||||
$options = $this->bi->genWaterPolo(json_decode($chart->fields, true), $setting, $chart->sql, $filters, $chart->driver);
|
||||
|
||||
$component->option->dataset = $options['series'][0]['data'][0];
|
||||
return $this->setComponentDefaults($component);
|
||||
@@ -2284,7 +2284,7 @@ class screenModel extends model
|
||||
if($settings and isset($settings->metric))
|
||||
{
|
||||
$sql = $this->setFilterSQL($chart);
|
||||
$results = $this->dao->query($sql)->fetchAll();
|
||||
$results = $this->bi->queryWithDriver($driver, $sql);
|
||||
$group = $settings->group[0]->field;
|
||||
|
||||
$metrics = array();
|
||||
@@ -2603,10 +2603,10 @@ class screenModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function buildDataset($chartID, $sql = '')
|
||||
public function buildDataset($chartID, $driver, $sql = '')
|
||||
{
|
||||
if(in_array($chartID, $this->config->screen->phpChart)) return $this->getDatasetForUsageReport($chartID);
|
||||
return $this->dao->query($sql)->fetchAll();
|
||||
return $this->bi->queryWithDriver($driver, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1914,7 +1914,7 @@ class treeModel extends model
|
||||
|
||||
if($self)
|
||||
{
|
||||
if($type == 'host') $module->root = 0;
|
||||
if($type == 'host' || !isset($module->root)) $module->root = 0;
|
||||
if($self->root && !isset($module->root)) $module->root = $self->root;
|
||||
if($self->parent != $module->parent || $self->root != $module->root)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,11 @@ class upgrade extends control
|
||||
$this->locate(inlink('backup'));
|
||||
}
|
||||
|
||||
public function importBIData()
|
||||
{
|
||||
$this->loadModel('install')->importBIData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权协议页面。
|
||||
* Check agree license.
|
||||
|
||||
@@ -7848,10 +7848,11 @@ class upgradeModel extends model
|
||||
{
|
||||
if(!$sql) return array();
|
||||
|
||||
$this->loadModel('bi');
|
||||
$this->loadModel('dataview');
|
||||
$this->loadModel('chart');
|
||||
|
||||
$columns = $this->dataview->getColumns($sql);
|
||||
$columns = $this->bi->getColumnsType($sql);
|
||||
$columnFields = array();
|
||||
foreach($columns as $column => $type) $columnFields[$column] = $column;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user