2include_once
'dbxDB.class.php';
69 parent::__construct();
87 $this->_max_step_runtime = $seconds;
110 if ($chunk_size > 0) {
111 $this->_chunk_size = $chunk_size;
144 return parent::load_dd(
$dd);
166 'dd_status' => $dd_sys[
'dd_status'] ?? 0,
167 'dd_modul' => $dd_sys[
'dd_modul'] ??
'',
168 'dd_name' => $dd_sys[
'dd_name'] ??
'',
186 $dd_status = $dd_sys[
'dd_status'] ?? 0;
187 $dd_modul = $dd_sys[
'dd_modul'] ??
'';
188 $dd_name = $dd_sys[
'dd_name'] ??
'';
190 if ($dd_modul && $dd_name && isset(
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name])) {
191 unset(
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name]);
200 $activ_modul =
dbx()->get_system_var(
'dbx_activ_modul',
'dbx');
202 if (strpos(
$dd,
'|') !==
false) {
203 $parts = explode(
'|',
$dd, 2);
204 $dd_modul = trim($parts[0]);
205 $dd_name = trim($parts[1]);
207 if ($dd_modul ===
'modul' || $dd_modul ===
'') {
208 $dd_modul = $activ_modul;
211 $dd_modul = $activ_modul;
215 if (isset(
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name])) {
216 unset(
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name]);
230 $dd_status = $dd_sys[
'dd_status'] ?? 0;
231 $dd_modul = $dd_sys[
'dd_modul'] ??
'';
232 $dd_name = $dd_sys[
'dd_name'] ??
'';
234 if ($dd_status <= 0) {
238 return $_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name][
'indexes'] ?? [];
256 $dd_status = $dd_sys[
'dd_status'] ?? 0;
257 $dd_modul = $dd_sys[
'dd_modul'] ??
'';
258 $dd_name = $dd_sys[
'dd_name'] ??
'';
260 if ($dd_status <= 0) {
264 $table =
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name][
'table'] ?? [];
271 'fields' =>
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name][
'fields'] ?? [],
272 'indexes' =>
$_SESSION[
'dbx'][
'cache'][
'dd'][$dd_modul][$dd_name][
'indexes'] ?? [],
290 $activ_modul =
dbx()->get_system_var(
'dbx_activ_modul',
'dbx');
292 if (strpos(
$dd,
'|') !==
false) {
293 $parts = explode(
'|',
$dd, 2);
294 $dd_modul = trim($parts[0]);
295 $dd_name = trim($parts[1]);
297 if ($dd_modul ===
'modul' || $dd_modul ===
'') {
298 $dd_modul = $activ_modul;
301 $dd_modul = $activ_modul;
305 $dd_file1 =
dbx()->os_path(
dbx()->
get_base_dir() .
'dbx/modules/' . $dd_modul .
'/dd/' . $dd_name .
'.dd.php');
306 $dd_file2 =
dbx()->os_path(
dbx()->
get_base_dir() .
'dbx/modules/dbx/dd/' . $dd_name .
'.dd.php');
308 return file_exists($dd_file1) || file_exists($dd_file2);
326 if (file_exists(
$file)) {
330 return dbx()->os_path(
dbx()->
get_base_dir() .
'dbx/modules/dbx/tpl/php/' . $name .
'.php');
359 if (!file_exists(
$file)) {
360 dbx()->sys_msg(
'error',
'dd', $template_name,
'missing template',
$file);
370 foreach ($vars as $key =>
$value) {
372 foreach (
$value as $k => $v) {
376 $replace[
'{' . $key .
'}'] = (string)
$value;
394 $modul =
dbx()->get_system_var(
'dbx_activ_modul',
'dbx');
448 $fields_block = trim($fields_block);
455 $indexes_block = trim($indexes_block);
458 'table_block' => $table_block,
459 'fields_block' => $fields_block,
460 'indexes_block' => $indexes_block,
464 dbx()->sys_msg(
'error',
'dd',
$dd,
'write_dd',
'empty dd content');
472 @mkdir(
$dir, 0777,
true);
477 dbx()->sys_msg(
'error',
'dd',
$dd,
'write_dd',
$file);
578 'server' =>
$table[
'server'] ??
'dbXsystem',
589 'default_sort' =>
'',
590 'form-dd-table'=>
'',
598 'create_owner' =>
'*',
599 'update_owner' =>
'*',
600 'delete_owner' =>
'*',
603 foreach ($defaults as $k => $v) {
645 foreach ($defaults as $k => $v) {
671 foreach ($defaults as $k => $v) {
689 return in_array(strtolower($name), [
'id',
'create_date',
'create_uid',
'update_date',
'update_uid',
'owner'],
true);
702 return str_replace(
'_',
' ', ucfirst($name));
717 $name = strtolower((
string)(
$field[
'name'] ??
''));
718 $type = strtolower((
string)(
$field[
'type'] ??
'varchar'));
719 $length = trim((
string)(
$field[
'length'] ??
''));
721 if ($name ===
'id') {
725 if (in_array($type, [
'bool',
'boolean',
'bit'],
true)) {
729 if (in_array($type, [
'int',
'integer',
'bigint',
'smallint',
'mediumint',
'tinyint'],
true)) {
733 if (in_array($type, [
'decimal',
'numeric'],
true)) {
737 if (in_array($type, [
'float',
'double',
'real'],
true)) {
741 if (in_array($type, [
'date',
'datetime',
'timestamp'],
true)) {
745 if ($type ===
'time' || $type ===
'year') {
749 if (($type ===
'tinyint' || $type ===
'int') && $length ===
'1') {
765 $type = strtolower((
string)(
$field[
'type'] ??
'varchar'));
766 $length = trim((
string)(
$field[
'length'] ??
''));
768 if (in_array($type, [
'bool',
'boolean',
'bit'],
true)
769 || (in_array($type, [
'tinyint',
'int',
'integer'],
true) && $length ===
'1')) {
770 return 'checkbox-label';
773 if (in_array($type, [
'int',
'integer',
'bigint',
'smallint',
'mediumint',
'tinyint'],
true)) {
774 return 'integer-label';
777 if ($type ===
'date') {
781 if (in_array($type, [
'datetime',
'timestamp'],
true)) {
782 return 'datetime-label';
785 if (in_array($type, [
'text',
'mediumtext',
'longtext'],
true)) {
786 return 'textarea-label';
826 if (preg_match(
'/^([a-zA-Z0-9_]+)\s*(?:\((.*?)\))?/i', $type, $m)) {
828 'type' => strtolower($m[1]),
829 'length' => (string)($m[2] ??
''),
834 'type' => strtolower($type),
861 $dbType = strtolower(trim($dbType));
862 $rawType = strtolower(trim($rawType));
863 $length = trim($length);
865 if (in_array($rawType, [
'bool',
'boolean'],
true)) {
869 if (in_array($rawType, [
'tinyint',
'smallint',
'mediumint',
'bigint'],
true)) {
873 if (in_array($rawType, [
'int',
'integer',
'serial',
'number'],
true)) {
877 if (in_array($rawType, [
'bit'],
true)) {
881 if (in_array($rawType, [
'date',
'time',
'year'],
true)) {
885 if (in_array($rawType, [
'datetime',
'timestamp'],
true)) {
889 if (in_array($rawType, [
'char',
'nchar'],
true)) {
893 if (in_array($rawType, [
'varchar',
'varchar2',
'nvarchar',
'string'],
true)) {
897 if (in_array($rawType, [
'tinytext',
'mediumtext',
'longtext'],
true)) {
901 if (in_array($rawType, [
'text',
'clob'],
true)) {
905 if (in_array($rawType, [
'decimal',
'numeric'],
true)) {
909 if (in_array($rawType, [
'float',
'real'],
true)) {
913 if (in_array($rawType, [
'double'],
true)) {
917 if (in_array($rawType, [
'binary',
'varbinary',
'tinyblob',
'blob',
'mediumblob',
'longblob',
'json',
'enum',
'set'],
true)) {
921 if ($dbType ===
'sqlite') {
961 foreach (
$rows as $row) {
964 (
string)($row[
'Field'] ??
''),
966 (
string)($typeInfo[
'type'] ??
'varchar'),
967 (
string)($typeInfo[
'length'] ??
''),
968 ((
string)($row[
'Null'] ??
'YES') ===
'YES') ?
'1' :
'0',
969 $row[
'Default'] ??
'',
970 (
string)($row[
'Key'] ??
'')
979 foreach (
$rows as $row) {
981 $index = ((int)($row[
'pk'] ?? 0) === 1) ?
'PRI' :
'';
984 (
string)($row[
'name'] ??
''),
986 (
string)($typeInfo[
'type'] ??
'text'),
987 (
string)($typeInfo[
'length'] ??
''),
988 ((
int)($row[
'notnull'] ?? 0) === 1) ?
'0' :
'1',
989 $row[
'dflt_value'] ??
'',
997 SELECT column_name, data_type, character_maximum_length, is_nullable, column_default
998 FROM information_schema.columns
1000 ORDER BY ordinal_position
1004 foreach (
$rows as $row) {
1006 (
string)($row[
'column_name'] ??
''),
1008 (
string)($row[
'data_type'] ??
'text'),
1009 (
string)($row[
'character_maximum_length'] ??
''),
1010 ((
string)($row[
'is_nullable'] ??
'YES') ===
'YES') ?
'1' :
'0',
1011 $row[
'column_default'] ??
'',
1019 SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT
1020 FROM INFORMATION_SCHEMA.COLUMNS
1022 ORDER BY ORDINAL_POSITION
1026 foreach (
$rows as $row) {
1028 (
string)($row[
'COLUMN_NAME'] ??
''),
1030 (
string)($row[
'DATA_TYPE'] ??
'varchar'),
1031 (
string)($row[
'CHARACTER_MAXIMUM_LENGTH'] ??
''),
1032 ((
string)($row[
'IS_NULLABLE'] ??
'YES') ===
'YES') ?
'1' :
'0',
1033 $row[
'COLUMN_DEFAULT'] ??
'',
1041 SELECT column_name, data_type, data_length, nullable, data_default
1042 FROM user_tab_columns
1048 foreach (
$rows as $row) {
1050 (
string)($row[
'COLUMN_NAME'] ??
''),
1052 (
string)($row[
'DATA_TYPE'] ??
'varchar2'),
1053 (
string)($row[
'DATA_LENGTH'] ??
''),
1054 ((
string)($row[
'NULLABLE'] ??
'Y') ===
'Y') ?
'1' :
'0',
1055 $row[
'DATA_DEFAULT'] ??
'',
1085 $sql =
"SHOW INDEX FROM " . $this->
quote_ident($server, $tableName);
1089 foreach (
$rows as $row) {
1090 $keyName = (string)($row[
'Key_name'] ??
'');
1091 $colName = (string)($row[
'Column_name'] ??
'');
1092 $nonUnique = (int)($row[
'Non_unique'] ?? 1);
1094 if (!isset($grouped[$keyName])) {
1095 $grouped[$keyName] = [
1097 'type' => ($keyName ===
'PRIMARY') ?
'PRIMARY' : (($nonUnique === 0) ?
'UNIQUE' :
'INDEX'),
1099 'unique' => ($keyName ===
'PRIMARY' || $nonUnique === 0) ?
'1' :
'0',
1104 $grouped[$keyName][
'fields'][] = $colName;
1107 foreach ($grouped as $idx) {
1108 $idx[
'fields'] = implode(
',', $idx[
'fields']);
1114 $sql =
"PRAGMA index_list(" . $this->
quote_ident($server, $tableName) .
")";
1117 foreach (
$rows as $row) {
1118 $name = (string)($row[
'name'] ??
'');
1119 $unique = ((int)($row[
'unique'] ?? 0) === 1) ?
'1' :
'0';
1120 $origin = (string)($row[
'origin'] ??
'');
1122 $sql2 =
"PRAGMA index_info(" . $this->
quote_ident($server, $name) .
")";
1123 $cols = $this->
rawQuery($server, $sql2);
1126 foreach ($cols as $col) {
1127 $fields[] = (string)($col[
'name'] ??
'');
1131 if ($origin ===
'pk') {
1134 } elseif ($unique ===
'1') {
1141 'fields' => implode(
',',
$fields),
1142 'unique' => $unique,
1150 SELECT indexname, indexdef
1152 WHERE tablename = " . $this->
sql_quote($server, $tableName);
1155 foreach (
$rows as $row) {
1156 $name = (string)($row[
'indexname'] ??
'');
1157 $def = (string)($row[
'indexdef'] ??
'');
1159 preg_match(
'/\((.*?)\)/', $def, $m);
1163 $unique = (stripos($def,
'UNIQUE INDEX') !==
false) ?
'1' :
'0';
1164 $type = ($unique ===
'1') ?
'UNIQUE' :
'INDEX';
1170 'unique' => $unique,
1207 'index' => strtoupper((
string)
$index),
1208 'length' => $length,
1214 'placeholder' =>
'',
1261 return match ($dbType) {
1262 'mysql' =>
'`' . str_replace(
'`',
'``', $name) .
'`',
1263 'sqlsrv' =>
'[' . str_replace(
']',
']]', $name) .
']',
1264 default =>
'"' . str_replace(
'"',
'""', $name) .
'"',
1284 $type = strtolower(trim($type));
1285 $len = trim($length);
1289 return match ($type) {
1309 'longblob' =>
'BLOB',
1314 return match ($type) {
1315 'tinyint' =>
'TINYINT' . ($len !==
'' ?
'(' . $len .
')' :
''),
1316 'smallint' =>
'SMALLINT' . ($len !==
'' ?
'(' . $len .
')' :
''),
1317 'mediumint' =>
'MEDIUMINT' . ($len !==
'' ?
'(' . $len .
')' :
''),
1319 'integer' =>
'INT' . ($len !==
'' ?
'(' . $len .
')' :
'(11)'),
1320 'bigint' =>
'BIGINT' . ($len !==
'' ?
'(' . $len .
')' :
''),
1322 'numeric' =>
'DECIMAL' . ($len !==
'' ?
'(' . $len .
')' :
'(10,2)'),
1323 'float' =>
'FLOAT' . ($len !==
'' ?
'(' . $len .
')' :
''),
1325 'real' =>
'DOUBLE' . ($len !==
'' ?
'(' . $len .
')' :
''),
1326 'bit' =>
'BIT' . ($len !==
'' ?
'(' . $len .
')' :
'(1)'),
1327 'char' =>
'CHAR(' . ($len !==
'' ? $len :
'1') .
')',
1328 'binary' =>
'BINARY(' . ($len !==
'' ? $len :
'1') .
')',
1329 'varbinary' =>
'VARBINARY(' . ($len !==
'' ? $len :
'255') .
')',
1330 'tinytext' =>
'TINYTEXT',
1332 'mediumtext' =>
'MEDIUMTEXT',
1333 'longtext' =>
'LONGTEXT',
1334 'tinyblob' =>
'TINYBLOB',
1336 'mediumblob' =>
'MEDIUMBLOB',
1337 'longblob' =>
'LONGBLOB',
1339 'enum' => ($len !==
'' ?
'ENUM(' . $len .
')' :
'VARCHAR(255)'),
1340 'set' => ($len !==
'' ?
'SET(' . $len .
')' :
'VARCHAR(255)'),
1342 'time' =>
'TIME(6)',
1344 'datetime' =>
'DATETIME(6)',
1345 'timestamp' =>
'TIMESTAMP(6)',
1346 default =>
'VARCHAR(' . ($len !==
'' ? $len :
'255') .
')',
1350 return match ($type) {
1351 'int',
'integer',
'mediumint' =>
'INTEGER',
1352 'bigint' =>
'BIGINT',
1354 'tinyint' =>
'SMALLINT',
1356 'numeric' =>
'NUMERIC' . ($len !==
'' ?
'(' . $len .
')' :
''),
1359 'double' =>
'DOUBLE PRECISION',
1362 'timestamp' =>
'TIMESTAMP',
1365 'longtext' =>
'TEXT',
1366 'char' =>
'CHAR(' . ($len !==
'' ? $len :
'1') .
')',
1367 default =>
'VARCHAR(' . ($len !==
'' ? $len :
'255') .
')',
1371 return match ($type) {
1372 'int',
'integer',
'mediumint' =>
'INTEGER',
1373 'bigint' =>
'BIGINT',
1375 'tinyint' =>
'SMALLINT',
1377 'numeric' =>
'DECIMAL' . ($len !==
'' ?
'(' . $len .
')' :
''),
1383 'timestamp' =>
'TIMESTAMP',
1386 'longtext' =>
'TEXT',
1387 default =>
'VARCHAR(' . ($len !==
'' ? $len :
'255') .
')',
1403 $default = (string)$default;
1404 $u = strtoupper($default);
1406 if ($u ===
'CURRENT_TIMESTAMP') {
1407 return 'DEFAULT CURRENT_TIMESTAMP';
1411 'int',
'integer',
'bigint',
'smallint',
'mediumint',
'tinyint',
'bit',
1412 'decimal',
'numeric',
'float',
'double',
'real',
1415 if (in_array(strtolower($ddType), $numericTypes,
true) && is_numeric($default)) {
1416 return 'DEFAULT ' . $default;
1433 $name =
$field[
'name'] ??
'';
1434 $type = strtolower((
string)(
$field[
'type'] ??
'varchar'));
1435 $length = (string)(
$field[
'length'] ??
'');
1437 $default =
$field[
'default'] ??
'';
1442 if (
$index ===
'PRI' && strtolower($name) ===
'id') {
1448 return $this->
quote_ident(
$server, $name) .
' ' . $sqlType .
' AUTO_INCREMENT PRIMARY KEY';
1453 return $this->
quote_ident(
$server, $name) .
' INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
1458 return $this->
quote_ident(
$server, $name) .
' NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
1466 if ($default !==
'' && $default !==
null) {
1497 $name = strtolower(trim((
string)(
$field[
'name'] ??
'')));
1503 if ($name ===
'id') {
1504 $idField = array_merge(
$field, $idField);
1508 if (strtoupper((
string)(
$field[
'index'] ??
'')) ===
'PRI') {
1525 return in_array($dbType, array(
1526 'sqlite',
'mysql',
'pgsql',
'sqlsrv',
'oci',
'firebird',
1527 'cubrid',
'dblib',
'ibm',
'informix',
'odbc',
1546 $primaryFields = [];
1550 if (strtoupper((
string)(
$field[
'index'] ??
'')) ===
'PRI') {
1551 $isInlinePrimary = strtolower((
string)(
$field[
'name'] ??
'')) ===
'id'
1553 if (!$isInlinePrimary) {
1554 $primaryFields[] =
$field[
'name'];
1559 if ($primaryFields) {
1561 foreach ($primaryFields as $fld) {
1564 $parts[] =
'PRIMARY KEY (' . implode(
',', $quoted) .
')';
1568 $sql .=
' ' . implode(
",\n ", $parts) .
"\n";
1585 $name = (string)(
$index[
'name'] ??
'');
1586 $type = strtoupper((
string)(
$index[
'type'] ??
'INDEX'));
1587 $fields = array_filter(array_map(
'trim', explode(
',', (
string)(
$index[
'fields'] ??
''))));
1599 if ($type ===
'UNIQUE') {
1602 .
' (' . implode(
',', $quotedFields) .
')';
1603 } elseif ($type !==
'PRIMARY') {
1606 .
' (' . implode(
',', $quotedFields) .
')';
1610 return ($type ===
'PRIMARY') ? 1 : 0;
1628 if (!preg_match(
'/\.(db3|sqlite|sqlite3)$/i',
$server)) {
1633 $modul = $dd_sys[
'dd_modul'] ??
'';
1636 if (strpos(
$server,
'|') !==
false) {
1637 $parts = explode(
'|',
$server, 2);
1638 $modul = trim($parts[0]);
1639 $name = trim($parts[1]);
1642 if ($modul ===
'' || $modul ===
'modul') {
1643 $modul =
dbx()->get_system_var(
'dbx_activ_modul',
'dbx');
1646 if ($modul ===
'') {
1651 if (!is_dir(
$dir) && !@mkdir(
$dir, 0777,
true)) {
1652 dbx()->sys_msg(
'error',
'db',
$server,
'create sqlite dir failed',
$dir);
1657 if (!file_exists(
$file) && @file_put_contents(
$file,
'') ===
false) {
1658 dbx()->sys_msg(
'error',
'db',
$server,
'create sqlite file failed',
$file);
1662 $hostRel =
dbx()->config_path_store(
$dir,
true);
1667 'dbname' => basename(
$file),
1713 if (strtoupper((
string)(
$index[
'type'] ??
'INDEX')) ===
'PRIMARY') {
1781 $primaryFields = [];
1790 if (strtoupper((
string)(
$field[
'index'] ??
'')) ===
'PRI') {
1791 $isInlinePrimary = strtolower((
string)(
$field[
'name'] ??
'')) ===
'id'
1793 if (!$isInlinePrimary) {
1794 $primaryFields[] =
$field[
'name'];
1803 if ($primaryFields) {
1805 foreach ($primaryFields as $fld) {
1808 $parts[] =
'PRIMARY KEY (' . implode(
',', $quoted) .
')';
1812 $sql .=
' ' . implode(
",\n ", $parts) .
"\n";
1821 if (strtoupper((
string)(
$index[
'type'] ??
'INDEX')) ===
'PRIMARY') {
1885 $serverKey = strtolower(trim(
$server));
1886 $tableKey = strtolower(trim(
$table));
1888 if ($serverKey ===
'' || $tableKey ===
'') {
1894 $norm = str_replace(
'\\',
'/',
$file);
1895 if (!preg_match(
'#/dbx/modules/([^/]+)/dd/([^/]+)\.dd\.php$#', $norm, $match)) {
1901 if (
$dd ===
'new' ||
$dd ===
'') {
1905 $ddRef = $modul .
'|' .
$dd;
1911 $modelServer = strtolower((
string)($model[
'table'][
'server'] ??
''));
1912 $modelTable = strtolower((
string)($model[
'table'][
'table'] ??
''));
1913 $serverMatch = ($modelServer === $serverKey);
1915 if (!$serverMatch && strpos($serverKey,
'|') !==
false) {
1916 [$serverModul, $serverName] = array_pad(explode(
'|', $serverKey, 2), 2,
'');
1917 $serverMatch = ($serverModul === strtolower($modul))
1918 && ($modelServer === $serverName || $modelServer ===
'modul');
1921 if ($serverMatch && $modelTable === $tableKey) {
1948 if ($match && !
empty($match[
'model'][
'fields'])) {
1951 'dd_ref' => $match[
'dd_ref'],
1952 'fields' => $match[
'model'][
'fields'] ?? [],
1953 'indexes' => $match[
'model'][
'indexes'] ?? [],
1977 string $sourceServer,
1978 string $sourceTable,
1979 string $targetServer,
1980 string $targetTable =
'',
1981 string $mode =
'step',
1982 int $createTarget = 1,
1983 int $truncateTarget = 1
1985 $targetTable = $targetTable ?: $sourceTable;
1991 $key = $this->proc_key(
'transfer_table', [
2000 if (
$mode ===
'reset') {
2001 $this->clear_proc_state($key);
2004 'status' =>
'reset',
2005 'message' =>
'transfer state cleared',
2007 'step_percent' => 0,
2011 if (
$mode ===
'status') {
2012 $state = $this->get_proc_state($key);
2016 'message' =>
'no active state',
2018 'step_percent' => 0,
2022 if (in_array(
$mode, [
'pause',
'resume',
'continue',
'cancel'],
true)) {
2023 return $this->control_proc_state($key,
$mode);
2026 if (
$mode ===
'restart') {
2027 $this->clear_proc_state($key);
2031 $state = $this->get_proc_state($key);
2034 if (!$sourceServer || !$sourceTable || !$targetServer || !$targetTable) {
2035 return $this->proc_error([
'proc_key' => $key],
'source or target missing');
2038 if (!$this->get_table_exist($sourceServer, $sourceTable)) {
2039 return $this->proc_error([
'proc_key' => $key],
'source table not found');
2042 $state = $this->init_proc_state(
'transfer_table', $key, [
2043 'source_server' => $sourceServer,
2044 'source_table' => $sourceTable,
2045 'target_server' => $targetServer,
2046 'target_table' => $targetTable,
2047 'create_target' => $createTarget ? 1 : 0,
2048 'truncate_target' => $truncateTarget ? 1 : 0,
2049 'backup_file' => $this->build_backup_file_name($sourceServer, $sourceTable,
true),
2050 'phase' =>
'prepare_target',
2052 'message' =>
'transfer initialized',
2056 if ($this->proc_is_waiting(
$state)) {
2057 return $this->proc_response(
$state);
2060 switch (
$state[
'phase']) {
2061 case 'prepare_target':
2062 if (!$this->get_table_exist(
$state[
'target_server'],
$state[
'target_table'])) {
2063 if (empty(
$state[
'create_target'])) {
2064 $state = $this->proc_error(
$state,
'target table not found');
2065 $this->set_proc_state($key,
$state);
2069 $schema = $this->get_preferred_table_schema(
$state[
'source_server'],
$state[
'source_table']);
2075 $state = $this->proc_error(
$state,
'create target table failed');
2076 $this->set_proc_state($key,
$state);
2081 if (!empty(
$state[
'truncate_target'])) {
2082 $this->empty_db_table(
$state[
'target_server'],
$state[
'target_table']);
2085 $state[
'phase'] =
'backup_source';
2087 $state[
'step_percent'] = 100;
2088 $state[
'message'] =
'target ready';
2089 $this->set_proc_state($key,
$state);
2090 return $this->proc_response(
$state);
2092 case 'backup_source':
2095 if ((
$backup[
'status'] ??
'') ===
'error') {
2096 $state = $this->proc_error(
$state,
'backup source failed');
2097 $this->set_proc_state($key,
$state);
2101 if ((
$backup[
'status'] ??
'') !==
'finished') {
2102 $state[
'percent'] = 10 + (int)floor(((
$backup[
'percent'] ?? 0) * 0.4));
2105 $this->set_proc_state($key,
$state);
2106 return $this->proc_response(
$state);
2109 $state[
'phase'] =
'restore_target';
2111 $state[
'step_percent'] = 100;
2112 $state[
'message'] =
'backup finished';
2113 $this->set_proc_state($key,
$state);
2114 return $this->proc_response(
$state);
2116 case 'restore_target':
2117 $restore = $this->restore(
$state[
'target_server'],
$state[
'target_table'],
$state[
'backup_file'], [], 1);
2119 if (($restore[
'status'] ??
'') ===
'error') {
2120 $state = $this->proc_error(
$state,
'restore target failed');
2121 $this->set_proc_state($key,
$state);
2125 if (($restore[
'status'] ??
'') !==
'finished') {
2126 $state[
'percent'] = 55 + (int)floor((($restore[
'percent'] ?? 0) * 0.4));
2127 $state[
'step_percent'] = (int)($restore[
'percent'] ?? 0);
2128 $state[
'message'] = $restore[
'message'] ??
'restore target';
2129 $this->set_proc_state($key,
$state);
2130 return $this->proc_response(
$state);
2133 $state = $this->proc_finish(
$state,
'transfer finished');
2134 $this->set_proc_state($key,
$state);
2138 $state = $this->proc_error(
$state,
'unknown transfer phase');
2139 $this->set_proc_state($key,
$state);
2156 protected function proc_key(
string $type, array $parts): string
2158 return 'dbxdd_' . $type .
'_' . md5(json_encode($parts));
2170 $state =
dbx()->get_remember_var($key, [], $this->_remember_modul);
2184 dbx()->set_remember_var($key,
$state, $this->_remember_modul);
2196 dbx()->set_remember_var($key, [], $this->_remember_modul);
2210 $state[
'proc_type'] = $type;
2211 $state[
'proc_key'] = $key;
2216 $state[
'chunk_size'] =
$state[
'chunk_size'] ?? $this->_chunk_size;
2217 $state[
'step_maxsec'] =
$state[
'step_maxsec'] ?? $this->_max_step_runtime;
2218 $state[
'started_at'] =
$state[
'started_at'] ?? date(
'Y-m-d H:i:s');
2219 $state[
'updated_at'] = date(
'Y-m-d H:i:s');
2233 return in_array((
$state[
'status'] ??
''), [
'finished',
'error',
'paused',
'canceled'],
true);
2246 $cmd = strtolower(trim($cmd));
2248 if ($cmd ===
'restart') {
2252 'status' =>
'reset',
2253 'message' =>
'process restarted',
2255 'step_percent' => 0,
2256 'updated_at' => date(
'Y-m-d H:i:s'),
2265 'message' =>
'no active state',
2267 'step_percent' => 0,
2271 $status =
$state[
'status'] ??
'new';
2273 if ($cmd ===
'pause') {
2274 if (!in_array($status, [
'finished',
'error',
'canceled'],
true)) {
2275 $state[
'status'] =
'paused';
2276 $state[
'paused_at'] = date(
'Y-m-d H:i:s');
2277 $state[
'message'] =
'process paused';
2279 } elseif ($cmd ===
'resume' || $cmd ===
'continue') {
2280 if (in_array($status, [
'paused',
'canceled',
'new'],
true)) {
2281 $state[
'status'] =
'running';
2282 $state[
'resumed_at'] = date(
'Y-m-d H:i:s');
2283 $state[
'message'] = ($cmd ===
'continue') ?
'process continued' :
'process resumed';
2285 } elseif ($cmd ===
'cancel') {
2286 if (!in_array($status, [
'finished',
'error'],
true)) {
2287 $state[
'status'] =
'canceled';
2288 $state[
'canceled_at'] = date(
'Y-m-d H:i:s');
2289 $state[
'message'] =
'process canceled';
2307 $state[
'updated_at'] = date(
'Y-m-d H:i:s');
2321 $state[
'status'] =
'error';
2322 $state[
'message'] = $message;
2338 $state[
'status'] =
'finished';
2339 $state[
'message'] = $message;
2341 $state[
'step_percent'] = 100;
2342 $state[
'finished_at'] = date(
'Y-m-d H:i:s');
2353 return microtime(
true);
2366 return (microtime(
true) - $started_at) < $max_seconds;
2383 return strtolower(preg_replace(
'/[^a-z0-9]+/i',
'', trim($name)));
2395 $value = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'_', trim(
$value));
2409 if (!is_dir(
$dir)) {
2410 @mkdir(
$dir, 0777,
true);
2426 $kind = strtolower(trim($kind));
2430 $context[
'modul'] ??
'',
2431 $context[
'dd'] ??
'',
2432 $context[
'source_server'] ?? ($context[
'server'] ??
''),
2433 $context[
'source_table'] ?? ($context[
'table'] ??
''),
2434 $context[
'target_server'] ??
'',
2435 $context[
'target_table'] ??
'',
2455 $name = (string)(
$field[
'name'] ??
'');
2468 foreach (
$keys as $key) {
2469 if (array_key_exists($key, $record)) {
2470 $sorted[$key] = $record[$key];
2471 unset($record[$key]);
2475 foreach ($record as $key =>
$value) {
2493 $sourceByLower = [];
2494 foreach ($sourceFields as
$field) {
2495 $name = (string)(
$field[
'name'] ??
'');
2497 $sourceByLower[strtolower($name)] = $name;
2501 $targetByLower = [];
2502 foreach ($targetFields as
$field) {
2503 $name = (string)(
$field[
'name'] ??
'');
2505 $targetByLower[strtolower($name)] = $name;
2511 $sourceKey = strtolower(trim((
string)
$source));
2512 $targetKey = strtolower(trim((
string)
$target));
2514 if ($sourceKey ===
'' || $targetKey ===
'') {
2518 if (!isset($sourceByLower[$sourceKey]) || !isset($targetByLower[$targetKey])) {
2522 $sourceName = $sourceByLower[$sourceKey];
2523 $targetName = $targetByLower[$targetKey];
2525 foreach ($clean as $oldSource => $oldTarget) {
2526 if (strcasecmp($oldTarget, $targetName) === 0) {
2527 unset($clean[$oldSource]);
2531 $clean[$sourceName] = $targetName;
2552 foreach ($sourceFields as
$field) {
2553 $name = (string)(
$field[
'name'] ??
'');
2558 $sourceExact[strtolower($name)] = $name;
2560 if ($norm !==
'' && !isset($sourceNorm[$norm])) {
2561 $sourceNorm[$norm] = $name;
2568 foreach ($targetFields as
$field) {
2577 $usedSource[strtolower(
$source)] =
true;
2581 foreach ($targetFields as
$field) {
2587 $alreadyMapped =
false;
2588 foreach ($mapping as $mappedTarget) {
2589 if (strcasecmp($mappedTarget,
$target) === 0) {
2590 $alreadyMapped =
true;
2595 if ($alreadyMapped) {
2600 $source = $sourceNorm[$norm] ??
'';
2603 $usedSource[strtolower(
$source)] =
true;
2609 foreach ($mapping as $oldSource => $oldTarget) {
2610 if (strcasecmp($oldSource,
$source) === 0 || strcasecmp($oldTarget,
$target) === 0) {
2611 unset($mapping[$oldSource]);
2633 if (!is_file(
$file)) {
2635 'kind' => strtolower(trim($kind)),
2636 'context' => $context,
2643 $data = json_decode((
string)file_get_contents(
$file),
true);
2644 if (!is_array($data)) {
2648 $data[
'kind'] = $data[
'kind'] ?? strtolower(trim($kind));
2649 $data[
'context'] = is_array($data[
'context'] ??
null) ? $data[
'context'] : $context;
2650 $data[
'mapping'] = is_array($data[
'mapping'] ??
null) ? $data[
'mapping'] : [];
2651 $data[
'file'] =
$file;
2668 return is_array($data[
'mapping'] ??
null) ? $data[
'mapping'] : [];
2681 $kind = strtolower(trim($kind));
2686 $modul = (string)($context[
'modul'] ??
'');
2687 $dd = (string)($context[
'dd'] ??
'');
2688 $server = (string)($context[
'server'] ?? ($context[
'source_server'] ??
''));
2689 $table = (string)($context[
'table'] ?? ($context[
'source_table'] ??
''));
2695 $ddRef = ($modul &&
$dd && strpos(
$dd,
'|') ===
false) ? $modul .
'|' .
$dd :
$dd;
2697 if ($kind ===
'db_to_dd') {
2710 $targetFields = $oldModel[
'fields'] ?? $sourceFields;
2712 $targetLabel = ($modul ? $modul .
'|' :
'') .
$dd;
2713 } elseif ($kind ===
'transfer') {
2714 $sourceServer = (string)($context[
'source_server'] ??
$server);
2715 $sourceTable = (string)($context[
'source_table'] ??
$table);
2716 $targetServer = (string)($context[
'target_server'] ??
'');
2717 $targetTable = (string)($context[
'target_table'] ??
'');
2719 if ($sourceServer && $sourceTable && $this->
get_table_exist($sourceServer, $sourceTable)) {
2721 $sourceFields =
$schema[
'fields'] ?? [];
2723 if ($targetServer && $targetTable && $this->
get_table_exist($targetServer, $targetTable)) {
2724 $targetFields = $this->
get_db_fields($targetServer, $targetTable);
2726 $targetFields = $sourceFields;
2729 $sourceLabel = $sourceServer .
'|' . $sourceTable;
2730 $targetLabel = $targetServer .
'|' . $targetTable;
2734 if (!$model &&
$dd) {
2735 $model = $this->get_dd_model(
$dd);
2739 $table =
$table ?: (string)($model[
'table'][
'table'] ??
'');
2745 $targetFields = $model[
'fields'] ?? [];
2747 $targetLabel = ($modul ? $modul .
'|' :
'') .
$dd;
2750 $context[
'modul'] = $modul;
2751 $context[
'dd'] =
$dd;
2753 $context[
'table'] =
$table;
2756 $stored = is_array($storedData[
'mapping'] ??
null) ? $storedData[
'mapping'] : [];
2763 foreach ($targetFields as
$target) {
2764 $targetName = (string)(
$target[
'name'] ??
'');
2765 if ($targetName ===
'') {
2770 foreach ($mapping as
$source => $mappedTarget) {
2771 if (strcasecmp($mappedTarget, $targetName) === 0) {
2773 $usedSources[strtolower(
$source)] =
true;
2778 $source = $sourceName !==
'' && isset($sourceByName[$sourceName]) ? $sourceByName[$sourceName] : [];
2780 if ($sourceName !==
'') {
2781 $status = (strcasecmp($sourceName, $targetName) === 0) ?
'exact' :
'mapped';
2784 $status =
'type_conflict';
2791 'source_name' => $sourceName,
2792 'target_name' => $targetName,
2793 'status' => $status,
2797 $unmappedSources = [];
2798 foreach ($sourceFields as
$source) {
2799 $name = (string)(
$source[
'name'] ??
'');
2800 if ($name !==
'' &&
empty($usedSources[strtolower($name)])) {
2807 'context' => $context,
2808 'source_label' => $sourceLabel,
2809 'target_label' => $targetLabel,
2810 'source_fields' => $sourceFields,
2811 'target_fields' => $targetFields,
2813 'unmapped_sources' => $unmappedSources,
2814 'mapping' => $mapping,
2817 'updated_at' => $storedData[
'updated_at'] ??
'',
2836 'kind' => $model[
'kind'] ?? strtolower(trim($kind)),
2837 'context' => $model[
'context'] ?? $context,
2838 'source' => $model[
'source_label'] ??
'',
2839 'target' => $model[
'target_label'] ??
'',
2840 'mapping' => $clean,
2841 'updated_at' => date(
'Y-m-d H:i:s'),
2845 $ok = file_put_contents(
$file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
2847 return $ok ===
false ? 0 : 1;
2876 dbx()->sys_msg(
'error',
'dd',
$dd,
'create_dd',
'table not found');
2908 if (!is_dir(
$dir)) {
2909 @mkdir(
$dir, 0777,
true);
2912 $name =
$table .
'_' . date(
'Ymd_His') .
'.ddb';
2917 return $dir . $name;
2932 @unlink($finalFile);
2933 return @rename($tmpFile, $finalFile) ? 1 : 0;
2936 if (!class_exists(
'ZipArchive')) {
2937 $raw = preg_replace(
'/\.zip$/i',
'', $finalFile);
2939 return @rename($tmpFile, $raw) ? 1 : 0;
2942 $zipObj =
new ZipArchive();
2943 if ($zipObj->open($finalFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !==
true) {
2947 $inner = basename(preg_replace(
'/\.zip$/i',
'', $finalFile));
2948 $zipObj->addFile($tmpFile, $inner);
2985 return $this->
proc_error([
'proc_key' => $key],
'server or table missing');
2989 return $this->
proc_error([
'proc_key' => $key],
'table not found');
2994 return $this->
proc_error([
'proc_key' => $key],
'no fields found');
3001 $columns[] = $f[
'name'];
3002 $fieldTypes[$f[
'name']] = strtolower((
string)($f[
'type'] ??
''));
3003 if (strtoupper((
string)($f[
'index'] ??
'')) ===
'PRI' && !$pk) {
3010 return $this->
proc_error([
'proc_key' => $key],
'count failed');
3017 $tmpFile =
$file .
'.part';
3024 'backup_date' => date(
'Y-m-d H:i:s'),
3025 'row_count' => $total,
3027 'zip' => $zip ? 1 : 0,
3030 file_put_contents($tmpFile, json_encode([
'meta' => $meta], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) .
"\n");
3031 file_put_contents($tmpFile, json_encode([
3032 'columns' => $columns,
3033 'field_types' => $fieldTypes,
3034 ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) .
"\n", FILE_APPEND);
3040 'tmp_file' => $tmpFile,
3041 'zip' => $zip ? 1 : 0,
3042 'columns' => $columns,
3047 'message' =>
'backup initialized',
3067 $chunk = (int)
$state[
'chunk_size'];
3069 foreach (
$state[
'columns'] as $col) {
3079 if ($dbType ===
'mysql') {
3080 $limit =
' LIMIT ' . (int)
$state[
'offset'] .
', ' . $chunk;
3082 $limit =
' LIMIT ' . $chunk .
' OFFSET ' . (int)
$state[
'offset'];
3085 $sql =
'SELECT ' . implode(
',', $cols);
3090 if (!is_array(
$rows)) {
3099 $state[
'step_percent'] = 100;
3104 foreach (
$rows as $row) {
3106 foreach (
$state[
'columns'] as $col) {
3107 $rec[] = $row[$col] ??
null;
3114 json_encode([
'records' => $records], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) .
"\n",
3149 if (!class_exists(
'ZipArchive')) {
3150 $raw = preg_replace(
'/\.zip$/i',
'',
$file);
3151 return file_exists($raw) ? $raw :
'';
3155 if (!is_dir($tmpDir)) {
3156 @mkdir($tmpDir, 0777,
true);
3159 $tmpFile = $tmpDir . md5(
$file) .
'.ddb';
3161 if (file_exists($tmpFile)) {
3165 $zipObj =
new ZipArchive();
3166 if ($zipObj->open(
$file) !==
true) {
3170 if ($zipObj->numFiles < 1) {
3175 $content = $zipObj->getFromIndex(0);
3182 file_put_contents($tmpFile,
$content);
3219 return $this->
proc_error([
'proc_key' => $key],
'server, table or file missing');
3223 return $this->
proc_error([
'proc_key' => $key],
'target table not found');
3228 return $this->
proc_error([
'proc_key' => $key],
'restore source file not readable');
3233 return $this->
proc_error([
'proc_key' => $key],
'restore open source failed');
3236 $line1 = fgets($fh);
3237 $line2 = fgets($fh);
3239 $metaRec = json_decode((
string)$line1,
true);
3240 $colRec = json_decode((
string)$line2,
true);
3242 if (!is_array($metaRec) || !isset($metaRec[
'meta']) || !is_array($colRec) || !isset($colRec[
'columns'])) {
3244 return $this->
proc_error([
'proc_key' => $key],
'invalid backup format');
3247 $filePos = ftell($fh);
3253 foreach ($targetFields as $f) {
3254 $targetLookup[$f[
'name']] =
true;
3255 $targetTypes[$f[
'name']] = strtolower((
string)($f[
'type'] ??
''));
3259 return $this->
proc_error([
'proc_key' => $key],
'target table not found after empty');
3267 'zip' => $zip ? 1 : 0,
3268 'mapping' => is_array($mapping) ? $mapping : [],
3269 'source_columns' => $colRec[
'columns'],
3270 'source_types' => is_array($colRec[
'field_types'] ??
null)
3271 ? $colRec[
'field_types']
3273 'target_lookup' => $targetLookup,
3274 'target_types' => $targetTypes,
3276 'file_pos' => $filePos,
3278 'total' => (
int)($metaRec[
'meta'][
'row_count'] ?? 0),
3279 'message' =>
'restore initialized',
3291 $fh = fopen(
$state[
'source_file'],
'rb');
3298 fseek($fh, (
int)
$state[
'file_pos']);
3303 if ($line ===
false) {
3307 @unlink(
$state[
'source_file']);
3315 $rec = json_decode(trim($line),
true);
3316 if (!is_array($rec) || !isset($rec[
'records']) || !is_array($rec[
'records'])) {
3320 foreach ($rec[
'records'] as $rowValues) {
3322 foreach (
$state[
'source_columns'] as $i => $col) {
3323 $assoc[$col] = $rowValues[$i] ??
null;
3326 if (is_array(
$state[
'mapping'])) {
3327 foreach (
$state[
'mapping'] as $old => $new) {
3328 if ((
string)$old === (
string)$new) {
3332 if (array_key_exists($old, $assoc)) {
3333 $assoc[$new] = $assoc[$old];
3334 unset($assoc[$old]);
3340 foreach ($assoc as $fld => $val) {
3341 if (isset(
$state[
'target_lookup'][$fld])) {
3343 (
string)(
$state[
'target_db_type'] ??
''),
3344 (
string)(
$state[
'target_types'][$fld] ??
''),
3346 (
string)(
$state[
'source_types'][$fld] ??
'')
3351 if (isset($dbRec[
'id']) && ($dbRec[
'id'] ===
'' || $dbRec[
'id'] ===
null || $dbRec[
'id'] === 0 || $dbRec[
'id'] ===
'0')) {
3352 unset($dbRec[
'id']);
3364 .
' VALUES (' . implode(
',', $placeholders) .
')';
3366 $stmt = $this->db[
$state[
'server']]->prepare($sql);
3367 $stmt->execute(array_values($dbRec));
3372 $state[
'file_pos'] = ftell($fh);
3392 string $targetDbType,
3393 string $targetFieldType,
3395 string $sourceFieldType =
''
3397 $targetDbType = strtolower(trim($targetDbType));
3398 $targetFieldType = strtolower(trim($targetFieldType));
3399 $sourceFieldType = strtolower(trim($sourceFieldType));
3400 $temporalTypes = [
'date',
'time',
'datetime',
'timestamp',
'year'];
3401 $effectiveFieldType = $targetFieldType;
3405 if ($targetDbType ===
'sqlite'
3406 && in_array($targetFieldType, [
'',
'text',
'varchar'],
true)
3407 && in_array($sourceFieldType, $temporalTypes,
true)
3409 $effectiveFieldType = $sourceFieldType;
3412 if ($targetDbType ===
'mysql'
3414 && in_array($effectiveFieldType, $temporalTypes,
true)
3419 if ($targetDbType ===
'sqlite'
3421 && in_array($effectiveFieldType, $temporalTypes,
true)
3422 && preg_match(
'/^(.+)\.([0-9]{1,6})$/',
$value, $match)
3424 $fraction = rtrim($match[2],
'0');
3425 if ($fraction !==
'' && strlen($fraction) < 3) {
3426 $fraction = str_pad($fraction, 3,
'0');
3428 return $match[1] . ($fraction !==
'' ?
'.' . $fraction :
'');
3456 if ($ddGroup === $dbGroup) {
3460 if (in_array($ddGroup, [
'string',
'text'],
true) && in_array($dbGroup, [
'string',
'text'],
true)) {
3464 if (in_array($ddGroup, [
'integer',
'bool'],
true) && in_array($dbGroup, [
'integer',
'bool'],
true)) {
3481 $type = strtolower(trim($type));
3482 $length = trim($length);
3488 if (in_array($type, [
'bool',
'boolean'],
true)) {
3492 if ($type ===
'bit') {
3493 return ($length ===
'' || $length ===
'1') ?
'bool' :
'integer';
3496 if ($type ===
'tinyint' && $length ===
'1') {
3500 if (in_array($type, [
'int',
'integer',
'smallint',
'mediumint',
'tinyint',
'bigint',
'serial',
'number'],
true)) {
3504 if (in_array($type, [
'decimal',
'numeric'],
true)) {
3508 if (in_array($type, [
'float',
'double',
'real'],
true)) {
3512 if ($type ===
'date' || $type ===
'year') {
3516 if ($type ===
'time') {
3520 if (in_array($type, [
'datetime',
'timestamp'],
true)) {
3524 if (in_array($type, [
'char',
'nchar',
'varchar',
'varchar2',
'nvarchar',
'string'],
true)) {
3528 if (in_array($type, [
'text',
'tinytext',
'mediumtext',
'longtext',
'clob'],
true)) {
3532 if (in_array($type, [
'binary',
'varbinary',
'tinyblob',
'blob',
'mediumblob',
'longblob'],
true)) {
3536 if ($type ===
'json') {
3540 if (in_array($type, [
'enum',
'set'],
true)) {
3561 $dbEngine = strtolower(trim($dbEngine));
3562 $ddType = strtolower((
string)($ddField[
'type'] ??
''));
3563 $dbType = strtolower((
string)($dbField[
'type'] ??
''));
3564 $ddGroup = $this->
schema_type_group($ddType, (
string)($ddField[
'length'] ??
''));
3565 $dbGroup = $this->
schema_type_group($dbType, (
string)($dbField[
'length'] ??
''));
3567 if ($ddType !==
'' && $dbType !==
'' && ($ddType === $dbType || $this->
is_semantic_type_match($ddType, $dbType))) {
3571 if ($dbEngine ===
'sqlite') {
3572 if (in_array($ddGroup, [
'string',
'text',
'date',
'time',
'datetime',
'json',
'enum'],
true)
3573 && in_array($dbGroup, [
'string',
'text'],
true)) {
3577 if (in_array($ddGroup, [
'integer',
'bool'],
true) && in_array($dbGroup, [
'integer',
'bool'],
true)) {
3581 if (in_array($ddGroup, [
'decimal',
'float'],
true) && in_array($dbGroup, [
'decimal',
'float',
'integer'],
true)) {
3600 $dbEngine = strtolower(trim($dbEngine));
3601 $ddType = strtolower((
string)($ddField[
'type'] ??
''));
3602 $dbType = strtolower((
string)($dbField[
'type'] ??
''));
3604 if ($ddType ===
'' || $dbType ===
'') {
3608 if ($dbEngine ===
'sqlite') {
3612 if ($ddType === $dbType) {
3616 $ddGroup = $this->
schema_type_group($ddType, (
string)($ddField[
'length'] ??
''));
3617 $dbGroup = $this->
schema_type_group($dbType, (
string)($dbField[
'length'] ??
''));
3619 if (in_array($ddGroup, [
'integer',
'bool'],
true) && in_array($dbGroup, [
'integer',
'bool'],
true)) {
3623 if ($ddGroup ===
'decimal' && $dbGroup ===
'decimal') {
3627 if ($ddGroup ===
'float' && in_array($dbGroup, [
'float',
'decimal'],
true)) {
3648 $preserve = $compatible
3649 ? [
'type',
'index',
'length',
'default',
'label',
'rules',
'tooltip',
'errormsg',
'placeholder',
'convert',
'protect',
'group',
'mask',
'data',
'options',
'tpl',
'js']
3650 : [
'label',
'tooltip',
'errormsg',
'placeholder',
'convert',
'protect',
'group',
'mask',
'data',
'js'];
3652 foreach ($preserve as $key) {
3653 if (isset($oldField[$key]) && $oldField[$key] !==
'' && $oldField[$key] !==
null) {
3654 $merged[$key] = $oldField[$key];
3658 $merged[
'name'] = $dbField[
'name'] ?? ($oldField[
'name'] ??
'');
3660 if (
empty($merged[
'label'])) {
3664 if (
empty($merged[
'rules']) || !$compatible) {
3668 if (
empty($merged[
'tpl']) || !$compatible) {
3694 'server' => $model[
'table'][
'server'] ??
'',
3695 'table' => $model[
'table'][
'table'] ??
'',
3696 'table_exists' => 0,
3697 'create_table' => 0,
3699 'missing_in_dd' => [],
3700 'type_conflicts' => [],
3701 'add_indexes' => [],
3702 'rebuild_needed' => 0,
3703 'backup_file' =>
'',
3713 $ddFields = $model[
'fields'] ?? [];
3714 $ddIndexes = $model[
'indexes'] ?? [];
3722 $plan[
'table_exists'] = $tableExists;
3724 if (!$tableExists) {
3725 $plan[
'create_table'] = 1;
3732 $dbFieldsByName = [];
3733 foreach ($dbFields as
$field) {
3737 $ddFieldsByName = [];
3738 foreach ($ddFields as
$field) {
3744 foreach ($ddFields as
$field) {
3745 $name = strtolower((
string)
$field[
'name']);
3746 if (!isset($dbFieldsByName[$name])) {
3751 $dbf = $dbFieldsByName[$name];
3753 $ddType = strtolower((
string)(
$field[
'type'] ??
''));
3754 $dbType = strtolower((
string)($dbf[
'type'] ??
''));
3756 $ddLen = (string)(
$field[
'length'] ??
'');
3757 $dbLen = (string)($dbf[
'length'] ??
'');
3760 $lenEqual = ($ddLen === $dbLen) || ($ddLen ===
'' || $dbLen ===
'');
3767 if ($targetDbType ===
'sqlite') {
3773 if (!$typeEqual || !$lenEqual) {
3774 $plan[
'type_conflicts'][] = [
3775 'field' =>
$field[
'name'],
3776 'dd_type' => $ddType,
3777 'db_type' => $dbType,
3778 'dd_length' => $ddLen,
3779 'db_length' => $dbLen,
3784 foreach ($dbFields as
$field) {
3785 $name = strtolower((
string)
$field[
'name']);
3786 if (!isset($ddFieldsByName[$name])) {
3792 foreach ($dbIndexes as $idx) {
3793 $dbIndexNames[strtolower((
string)$idx[
'name'])] =
true;
3796 foreach ($ddIndexes as $idx) {
3797 $name = strtolower((
string)($idx[
'name'] ??
''));
3802 if (strtoupper((
string)($idx[
'type'] ??
'INDEX')) ===
'PRIMARY') {
3806 if (!isset($dbIndexNames[$name])) {
3807 $plan[
'add_indexes'][] = $idx;
3844 if (
$mode ===
'step') {
3848 $ddRef = ($modul && strpos((
string)
$dd,
'|') ===
false) ? $modul .
'|' .
$dd : (string)
$dd;
3849 $key = $this->
proc_key(
'sync_dd_to_db', [$modul,
$dd]);
3851 if (
$mode ===
'reset') {
3855 'status' =>
'reset',
3856 'message' =>
'sync state cleared',
3858 'step_percent' => 0,
3862 if (
$mode ===
'status') {
3867 'message' =>
'no active state',
3869 'step_percent' => 0,
3873 if (in_array(
$mode, [
'pause',
'resume',
'continue',
'cancel'],
true)) {
3877 if (
$mode ===
'restart') {
3884 if (in_array(
$mode, [
'check',
'plan'],
true)) {
3885 $plan[
'status'] =
'finished';
3886 $plan[
'message'] =
'plan ready';
3887 $plan[
'percent'] = 100;
3888 $plan[
'step_percent'] = 100;
3900 'server' =>
$plan[
'server'],
3901 'table' =>
$plan[
'table'],
3902 'phase' =>
'prepare',
3907 'message' =>
'sync initialized',
3911 'server' =>
$plan[
'server'],
3912 'table' =>
$plan[
'table'],
3914 'backup_started' => 0,
3915 'restore_started' => 0,
3927 switch (
$state[
'phase']) {
3930 $state[
'phase'] =
'create_table';
3931 $state[
'message'] =
'create table';
3933 $state[
'step_percent'] = 0;
3939 if (!in_array(
$state[
'mode'], [
'force',
'rebuild'],
true)) {
3945 $state[
'phase'] =
'backup_old';
3946 $state[
'message'] =
'backup old table';
3948 $state[
'step_percent'] = 0;
3953 $state[
'phase'] =
'add_fields';
3954 $state[
'message'] =
'add missing fields';
3956 $state[
'step_percent'] = 0;
3960 case 'create_table':
3962 $state[
'phase'] =
'add_indexes';
3963 $state[
'message'] =
'create indexes';
3965 $state[
'step_percent'] = 100;
3988 $state[
'percent'] = 20 + (int)floor((
$state[
'field_pos'] / $max) * 40);
3989 $state[
'step_percent'] = (int)floor((
$state[
'field_pos'] / $max) * 100);
3990 $state[
'message'] =
'add field ' . (
$field[
'name'] ??
'');
3994 $state[
'phase'] =
'add_indexes';
3995 $state[
'message'] =
'add indexes';
3997 $state[
'step_percent'] = 100;
4019 $state[
'percent'] = 70 + (int)floor((
$state[
'index_pos'] / $max) * 20);
4020 $state[
'step_percent'] = (int)floor((
$state[
'index_pos'] / $max) * 100);
4021 $state[
'message'] =
'add index ' . ($idx[
'name'] ??
'');
4039 if (($bak[
'status'] ??
'') ===
'error') {
4045 if (($bak[
'status'] ??
'') !==
'finished') {
4046 $state[
'percent'] = 10 + (int)floor((($bak[
'percent'] ?? 0) * 0.3));
4047 $state[
'step_percent'] = (int)($bak[
'percent'] ?? 0);
4048 $state[
'message'] =
'backup old table';
4053 $state[
'phase'] =
'rename_old';
4055 $state[
'step_percent'] = 100;
4056 $state[
'message'] =
'rename old table';
4061 $tmpOld =
$table .
'__dbxold_' . date(
'YmdHis');
4071 $state[
'old_table'] = $tmpOld;
4072 $state[
'phase'] =
'create_new';
4074 $state[
'step_percent'] = 100;
4075 $state[
'message'] =
'create new table';
4086 $state[
'phase'] =
'restore_new';
4088 $state[
'step_percent'] = 100;
4089 $state[
'message'] =
'restore data into new table';
4094 $mapping = is_array(
$state[
'mapping'] ??
null) ?
$state[
'mapping'] : [];
4097 if (($restore[
'status'] ??
'') ===
'error') {
4103 if (($restore[
'status'] ??
'') !==
'finished') {
4104 $state[
'percent'] = 65 + (int)floor((($restore[
'percent'] ?? 0) * 0.25));
4105 $state[
'step_percent'] = (int)($restore[
'percent'] ?? 0);
4106 $state[
'message'] =
'restore data into new table';
4111 $state[
'phase'] =
'drop_old';
4113 $state[
'step_percent'] = 100;
4114 $state[
'message'] =
'drop old table';
4201 if (
$mode ===
'reset') {
4205 'status' =>
'reset',
4206 'message' =>
'sync state cleared',
4208 'step_percent' => 0,
4212 if (
$mode ===
'status') {
4217 'message' =>
'no active state',
4219 'step_percent' => 0,
4223 if (in_array(
$mode, [
'pause',
'resume',
'continue',
'cancel'],
true)) {
4227 if (
$mode ===
'restart') {
4236 $syncServer =
$server ?:
'dbXsystem';
4246 $syncServer = $oldModel[
'table'][
'server'] ?? $syncServer;
4249 $syncTable = $oldModel[
'table'][
'table'] ?? $syncTable;
4254 return $this->
proc_error([
'proc_key' => $key],
'table not found');
4261 'server' => $syncServer,
4262 'table' => $syncTable,
4263 'old_model' => $oldModel,
4267 'server' => $syncServer,
4268 'table' => $syncTable,
4270 'phase' =>
'read_schema',
4272 'message' =>
'sync initialized',
4280 switch (
$state[
'phase']) {
4284 $state[
'phase'] =
'merge_meta';
4286 $state[
'step_percent'] = 100;
4287 $state[
'message'] =
'schema loaded';
4292 $oldModel =
$state[
'old_model'] ?? [];
4295 'server' =>
$state[
'server'],
4296 'table' =>
$state[
'table'],
4299 if (
$state[
'mode'] ===
'merge' && $oldModel) {
4301 'autosync',
'version',
'cache',
'trash',
'trace',
4302 'read',
'create',
'update',
'delete',
4303 'read_owner',
'create_owner',
'update_owner',
'delete_owner'
4305 if (isset($oldModel[
'table'][$k])) {
4306 $newTable[$k] = $oldModel[
'table'][$k];
4311 $oldFieldsByName = [];
4312 foreach (($oldModel[
'fields'] ?? []) as
$field) {
4313 $oldFieldsByName[strtolower((
string)(
$field[
'name'] ??
''))] =
$field;
4316 $mapping = is_array(
$state[
'mapping'] ??
null) ?
$state[
'mapping'] : [];
4320 $metaName = $mapping[$name] ?? $name;
4321 $metaKey = strtolower((
string)$metaName);
4322 if (!isset($oldFieldsByName[$metaKey])) {
4324 $metaKey = strtolower((
string)$metaName);
4327 if (
$state[
'mode'] ===
'merge' && isset($oldFieldsByName[$metaKey])) {
4329 $oldFieldsByName[$metaKey],
4338 $oldIndexesByName = [];
4339 foreach (($oldModel[
'indexes'] ?? []) as
$index) {
4347 if (
$state[
'mode'] ===
'merge' && isset($oldIndexesByName[$name])) {
4349 foreach ($oldIndexesByName[$name] as $k => $v) {
4350 if ($v !==
'' && $v !==
null) {
4360 $state[
'new_table'] = $newTable;
4361 $state[
'new_fields'] = $newFields;
4362 $state[
'new_indexes'] = $newIndexes;
4363 $state[
'phase'] =
'write_dd';
4365 $state[
'step_percent'] = 100;
4366 $state[
'message'] =
'meta merged';
4374 $state[
'new_table'] ?? [],
4375 $state[
'new_fields'] ?? [],
4376 $state[
'new_indexes'] ?? []
4454 if (!is_dir($path)) {
4460 if (!str_ends_with(
$file,
'.dd.php')) {
4464 $dd = str_replace(
'.dd.php',
'',
$file);
4465 if (
$dd ===
'new') {
4470 if (($dd_sys[
'dd_status'] ?? 0) <= 0) {
Zentrale Datenbank- und DD-Systemklasse von DBX.
get_dd_autosync($dd, $rec=0)
Gibt das Autosync-Flag einer DD zurück.
get_table_exist($dd, $dbtab='')
Prueft, ob eine Tabelle existiert.
rawQuery(string $server, string $query)
Führt eine rohe SQL-Abfrage auf dem angegebenen Server aus.
sqlite_sequence_exists(string $server)
Prueft ohne Schema-Warnung, ob SQLite seine AUTOINCREMENT-Tabelle hat.
count($dd, $where='', $server='')
Zählt Datensätze einer DD oder Tabelle.
empty($dd)
Leert eine Tabelle anhand ihrer DD.
dbConnect($server, $dbType, $dbHost, $dbName='', $dbUser='', $dbPass='', $dbPort='')
Stellt eine Verbindung zu einer Datenbank her und speichert sie in $this->db[$server].
get_dd_table($dd, $rec=0)
Gibt den Tabellennamen oder die komplette Table-Definition einer DD zurück.
exec_query($server, $sql)
Führt eine generische SQL-Exec-Anweisung aus.
escape($string, $server)
Escaped einen String für die sichere Verwendung in einer SQL-Abfrage.
get_dd_server(string $dd)
Ermittelt den lokal wirksamen Server einer Datenbeschreibung.
get_db_type($server)
Ermittelt den Datenbanktyp für einen Server.
get_dd_fields($dd, $label=0)
Gibt die Felddefinitionen einer DD zurück.
connect_db_server(string $server)
Verbindet sich mit einem angegebenen Datenbankserver.
step_start_time()
Liefert einen Step-Startzeitpunkt.
init_proc_state(string $type, string $key, array $state)
Initialisiert einen technischen Prozessstatus.
get_dd_file_path(string $modul, string $dd)
Ermittelt den Dateipfad einer DD-Datei.
sync_dd_to_db($modul, $dd, $mode='step')
Synchronisiert ein DD technisch in Richtung DB.
get_schema_mapping_values(string $kind, array $context)
Gibt nur die gespeicherten Mapping-Werte zurueck.
get_step_runtime()
Gibt die maximale Laufzeit eines technischen Prozess-Schritts zurück.
save_schema_mapping(string $kind, array $context, array $mapping)
Speichert ein Schema-Mapping als wiederverwendbare Mapping-Datei.
get_preferred_table_schema(string $server, string $table)
Liefert die beste Strukturquelle fuer eine DB-Tabelle.
schema_mapping_file_part(string $value)
Baut einen sicheren Dateinamenbestandteil.
is_system_field(string $name)
Prüft, ob ein Feld ein DBX-Systemfeld ist.
map_db_type_to_dd_type(string $dbType, string $rawType, string $length='')
Mappt einen DB-Roh-Typ auf einen kanonischen DD-Typ.
build_backup_file_name(string $server, string $table, bool $zip=false)
Baut einen Backup-Dateinamen.
dd_template_value(mixed $value)
create_db_index(string $server, string $table, array $index)
Erzeugt einen DB-Index aus einer DD-Indexdefinition.
build_schema_mapping(string $kind, array $context)
Baut die Mapping-Ansicht fuer Admin-UI und Prozesse.
schema_fields_by_name(array $fields)
Baut einen Feldindex nach echtem Feldnamen.
build_create_table_sql(string $dd)
Baut das CREATE-TABLE-SQL aus einem DD.
get_chunk_size()
Gibt die Standard-Chunk-Größe für technische Prozesse zurück.
normalize_dd_field(array $field)
parse_sql_type(string $type)
Zerlegt einen SQL-Typ in Basis-Typ und Länge.
is_semantic_type_match(string $ddType, string $dbType)
Prüft, ob zwei Typen semantisch kompatibel sind.
get_db_fields($server, $tableName)
Liest die Feldstruktur einer DB-Tabelle und erzeugt daraus kanonische DD-Felddefinitionen.
prepare_restore_source_file(string $file, bool $zip)
Bereitet eine Restore-Quelldatei vor.
get_dd_template_file(string $name)
Ermittelt den absoluten Dateipfad eines DD-Templates.
finalize_backup_file(string $tmpFile, string $finalFile, bool $zip)
Finalisiert eine Backup-Datei optional als ZIP.
proc_key(string $type, array $parts)
Erzeugt einen technischen Prozessschlüssel.
sql_quote(string $server, mixed $value)
Quotet einen SQL-Wert für Roh-SQL.
add_db_field_from_dd(string $server, string $table, array $field)
Fügt ein Feld aus DD-Definition in eine bestehende DB-Tabelle ein.
normalize_schema_field_key(string $name)
Normalisiert einen Feldnamen fuer robuste Auto-Zuordnung.
dync_db_to_dd($modul, $dd, $mode='step', string $server='', string $table='')
Kompatibilitaetsalias fuer sync_db_to_dd().
build_sql_column_from_dd(string $server, array $field)
Baut eine SQL-Spaltendefinition aus einem DD-Feld.
get_dd_exist($dd)
Prüft, ob eine DD-Datei existiert.
uses_inline_auto_increment_id(string $dbType)
Liefert, ob der Treiber die Auto-ID zusammen mit PRIMARY KEY direkt an der Spalte definiert.
normalize_schema_mapping(array $mapping, array $sourceFields, array $targetFields)
Normalisiert eine technische Source->Target-Zuordnung.
clear_dd_cache(string $dd)
Löscht den DD-Cache für eine DD.
quote_ident(string $server, string $name)
Quotet einen SQL-Identifier abhängig vom DB-Typ.
create_db_tab_from_fields(string $server, string $table, array $fields, array $indexes=[])
Erzeugt eine DB-Tabelle direkt aus Feld-/Index-Metadaten.
normalize_index_record(array $index)
Normalisiert einen DD-Indexsatz.
build_dd_field_from_db_meta(string $name, string $dbType, string $rawType, string $length, string $isNull, mixed $default, string $index='')
Baut aus gelesenen DB-Metadaten einen DD-Feldsatz.
restore($server, $table, $file, $mapping=[], $zip=0)
Stellt Daten aus einem kompakten Tabellenbackup wieder her.
ensure_sqlite_db_file_for_dd(string $dd)
Legt eine fehlende SQLite-Datei fuer ein DD an, damit die Tabelle anschliessend ueber die bestehende ...
sync_db_to_dd($modul, $dd, $mode='step', string $server='', string $table='')
Synchronisiert eine physische Datenbanktabelle technisch in Richtung DD.
step_time_left(float $started_at, float $max_seconds)
Prüft, ob noch Laufzeit für den aktuellen Prozessschritt übrig ist.
empty_db_table(string $server, string $table)
Leert eine konkrete DB-Tabelle ohne DD-Aufloesung.
merge_dd_field_with_db_field(array $oldField, array $dbField, string $dbEngine='')
Fuehrt ein vorhandenes DD-Feld mit einer DB-Felddefinition zusammen.
map_dd_type_to_sql_type(string $dbType, string $type, string $length='')
Mappt einen kanonischen DD-Typ auf einen konkreten SQL-Typ des Zielsystems.
proc_finish(array $state, string $message='finished')
Erzeugt einen Fertig-Status für einen Prozess.
__construct()
Initialisiert dbxDD.
get_dd_tables($path='')
Liefert DD-Übersichtsdaten aus einem DD-Verzeichnis.
schema_type_group(string $type, string $length='')
Gruppiert DD-/DB-Feldtypen fachlich.
backup($server, $table, $file='', $zip=0)
Erstellt ein tabellenbasiertes Datenbackup.
transfer_table(string $sourceServer, string $sourceTable, string $targetServer, string $targetTable='', string $mode='step', int $createTarget=1, int $truncateTarget=1)
Transferiert eine DB-Tabelle serveruebergreifend als Schrittprozess.
get_dd_cache_info(string $dd)
Ermittelt die aufgelöste Cache-Position einer DD.
find_dd_for_db_table(string $server, string $table)
Sucht ein DD, das auf eine konkrete DB-Tabelle zeigt.
load_dd(string $dd)
Nutzt direkt die bestehende dbxDB-Logik.
proc_response(array $state)
Aktualisiert einen Prozessstatus für Rückgabe.
proc_is_waiting(array $state)
Prueft, ob ein Prozess aktuell keine weitere Arbeit ausfuehren darf.
render_dd_template(string $template_name, array $vars=[])
Rendert ein DD-Template lokal als PHP-Quelltext.
create_dd(string $modul, string $dd, string $server='dbXsystem', string $table='')
Erzeugt eine DD aus einer vorhandenen DB-Tabelle.
infer_rules_from_field(array $field)
Leitet eine Standard-Regel aus einem Feld ab.
normalize_restore_value(string $targetDbType, string $targetFieldType, mixed $value, string $sourceFieldType='')
Normalisiert treiberspezifisch problematische Restore-Werte.
set_chunk_size(int $chunk_size)
Setzt die Standard-Chunk-Größe für technische Prozesse.
set_proc_state(string $key, array $state)
Speichert einen technischen Prozessstatus.
load_schema_mapping(string $kind, array $context)
Laedt ein gespeichertes Schema-Mapping.
clear_proc_state(string $key)
Löscht einen technischen Prozessstatus.
get_db_indexes(string $server, string $tableName)
Liest die Indexstruktur einer DB-Tabelle.
save_dd(string $modul, string $dd, array $table, array $fields, array $indexes=[])
Speichert eine DD-Datei und leert danach den DD-Cache.
write_dd(string $modul, string $dd, array $table, array $fields, array $indexes=[])
Schreibt eine DD-Datei anhand der DD-Templates.
proc_error(array $state, string $message)
Erzeugt einen Fehlerstatus für einen Prozess.
control_proc_state(string $key, string $cmd)
Steuert einen vorhandenen Prozessstatus.
build_default_sql(string $server, mixed $default, string $ddType)
Baut einen DEFAULT-SQL-Teil für einen DD-Default.
get_dd_model(string $dd)
Gibt das komplette DD-Modell zurück.
normalize_table_record(string $dd, array $table)
Normalisiert einen DD-Tabellensatz.
enforce_auto_increment_id(array $fields)
Erzwingt die globale Tabellenidentitaet fuer jede neue Fachtabelle.
build_sync_plan_dd_to_db(string $dd)
Baut einen technischen Sync-Plan DD -> DB.
get_dd_indexes(string $dd)
Gibt die Indexdefinitionen einer DD zurück.
schema_mapping_path(string $kind, array $context)
Liefert den Pfad zur Mapping-Datei.
sort_schema_record(array $record, array $keys)
infer_tpl_from_field(array $field)
Leitet ein sinnvolles Form-Template aus einer Felddefinition ab.
normalize_field_record(array $field)
Normalisiert einen DD-Feldsatz.
normalize_default_value(mixed $value)
Normalisiert einen Default-Wert aus DB-Metadaten.
drop_db_tab(string $server, string $table)
Löscht eine Datenbanktabelle.
normalize_dd_index(array $index)
schema_mapping_dir()
Liefert das Verzeichnis fuer gespeicherte Schema-Mappings.
set_step_runtime(float $seconds)
Setzt die maximale Laufzeit eines technischen Prozess-Schritts.
auto_schema_mapping(array $sourceFields, array $targetFields, array $stored=[])
Baut ein Auto-Mapping und uebersteuert es mit gespeicherter Zuordnung.
create_db_tab(string $dd)
Erzeugt eine Datenbanktabelle aus einem DD.
get_proc_state(string $key)
Liest einen technischen Prozessstatus.
is_db_sync_field_match(string $dbEngine, array $ddField, array $dbField)
Prüft, ob ein DB-Feld physisch synchron zu einem DD-Feld ist.
infer_label_from_name(string $name)
Leitet ein Standard-Label aus dem Feldnamen ab.
is_dd_field_compatible_with_db(array $ddField, array $dbField, string $dbEngine='')
Prüft, ob ein DD-Feld fachlich zu einem DB-Feld passt.
get_base_dir(int $cutData=0)
Liefert das Basisverzeichnis der Installation.
get_file_dir()
Liefert das files/-Verzeichnis der Installation.
if( $base !==$expectedBase) if($files !==$expectedBase . 'files/') $stored
foreach(array('bootstrapRowColumns', 'setBootstrapColumnLayout', 'addBootstrapColumn', 'dissolveBootstrapColumns',) as $function) $keys
if(!is_dir($backupDir) &&!mkdir($backupDir, 0775, true) &&!is_dir($backupDir)) $backupFile
DBX schema administration.
foreach($pages as $page) $targetRows