Explorar el Código

updated assertTableStruct in pdo

Piotr Labudda hace 10 años
padre
commit
80c2667718
Se han modificado 2 ficheros con 183 adiciones y 365 borrados
  1. 125 142
      SE/se-lib/Core/Pdo.php
  2. 58 223
      SE/se-lib/Route/Notify.php

+ 125 - 142
SE/se-lib/Core/Pdo.php

@@ -88,30 +88,39 @@ class Core_Pdo extends PDO {
 	}
 
 	/*
+	 * TODO: update keys:
 	 * TODO: keys name may be different - try to find and connect with given schema?
 	 * TODO: remove old uniq keys?
 	 */
 	public function assertTableStruct($tblName, $expectedStruct, $params = array()) {
-		// $expectedStruct['t1'] = array('type'=>'varchar', 'max_length'=>300, 'default_value'=>null)
+		// TODO: make backup for table?
 		$expectedStruct = $this->_fixExpectedStruct($expectedStruct);
 		//DBG::_(true, true, "fixedEpectedStruct", $expectedStruct, __CLASS__, __FUNCTION__, __LINE__);
-		//DBG::_(true, true, "fixedEpectedStruct", $this->showCreateStructMysql($tblName, $expectedStruct, $params), __CLASS__, __FUNCTION__, __LINE__);
+		//DBG::_(true, true, "fixedEpectedStruct", $this->showCreateStruct($tblName, $expectedStruct, $params), __CLASS__, __FUNCTION__, __LINE__);
 		$struct = $this->getTableStruct($tblName);
-		$expectedFields = array_keys($expectedStruct);
+		$expectedFields = array();//array_keys($expectedStruct);
+		foreach ($expectedStruct as $fldName => $fld) {
+			if ('UNIQUE KEY' == $fld['type']) continue;
+			if ('KEY' == $fld['type']) continue;
+			$expectedFields[] = $fldName;
+		}
 		$currentFields = array_keys($struct);
 
 		$missingFields = array_diff($expectedFields, $currentFields);
 		DBG::_(true, true, "struct", $struct, __CLASS__, __FUNCTION__, __LINE__);
 		DBG::_(true, true, "missingFields", $missingFields, __CLASS__, __FUNCTION__, __LINE__);
 
-		foreach ($missingFields as $fldName => $expected) {
-			DBG::_(true, true, "TODO: add missing field[{$fldName}]:", $expected, __CLASS__, __FUNCTION__, __LINE__);
-			$sqlType = array();
-			$sqlType['type'] = $expected['type'];
-			$sqlType['null'] = ($expected['is_nullable'])? '' : 'NOT NULL';
-			$sqlType['default'] = ($expected['is_nullable'])? '' : 'NOT NULL';
-			$sqlAdd = "alter table CRM_NOTIFY add {$fldName} {$sqlType['type']} {$sqlType['null']} {$sqlType['default']}";
-			DBG::_(true, true, "TODO: sqlChange", $sqlAdd, __CLASS__, __FUNCTION__, __LINE__);
+		foreach ($missingFields as $fldName) {
+			$fld = $expectedStruct[$fldName];
+			DBG::_(true, true, "add missing field[{$fldName}]:", $fld, __CLASS__, __FUNCTION__, __LINE__);
+			$sqlFieldStruct = $this->showTableStructField($fldName, $fld);
+			if ($sqlFieldStruct) {
+				$sqlAdd = "alter table {$tblName} add {$sqlFieldStruct}";
+				DBG::_(true, true, "sqlAdd", $sqlAdd, __CLASS__, __FUNCTION__, __LINE__);
+				$this->exec($sqlAdd);
+			} else {
+				throw new Exception("Unimplemented type '{$fld['type']}': " . json_encode($fld));
+			}
 		}
 
 		$toUpdateFields = array_intersect($expectedFields, $currentFields);
@@ -120,69 +129,34 @@ class Core_Pdo extends PDO {
 			$current = $struct[$fldName];
 			$expected = $expectedStruct[$fldName];
 			$needChange = false;
-			$toChange = array();
-			$toChange['type'] = $current['type'];
-			$toChange['null'] = ($current['is_nullable'])? '' : 'NOT NULL';
-			// TODO: cannot determine when default is NULL
-			$toChange['default'] = '';
-			if (!empty($current['default_value'])) $toChange['default'] = "DEFAULT '{$current['default_value']}'";
-			if (is_null($current['default_value'])) $toChange['default'] = "DEFAULT NULL";
 
-			DBG::_(true, true, "TODO: update field[{$fldName}]:", array('expected'=>$expected,'current'=>$current), __CLASS__, __FUNCTION__, __LINE__);
+			$sqlFieldStruct = $this->showTableStructField($fldName, $expected);
+			if (!$sqlFieldStruct) throw new Exception("Unimplemented type '{$expected['type']}' for field '{$fldName}': " . json_encode($expected));
+			$sqlChange = "alter table `{$tblName}` change `{$fldName}` {$sqlFieldStruct}";
+			DBG::_(true, true, "DBG: sqlChange", $sqlChange, __CLASS__, __FUNCTION__, __LINE__);
+
+			//DBG::_(true, true, "TODO: update field[{$fldName}]:", array('expected'=>$expected,'current'=>$current), __CLASS__, __FUNCTION__, __LINE__);
 			if ($current['type'] != $expected['type']) {
-				throw new Exception("Unimplemented change field type from {$current['type']} to {$expected['type']}");
+				throw new Exception("Unimplemented change field type from '{$current['type']}' to '{$expected['type']}' for field '{$fldName}': " . json_encode($expected));
 			}
 
 			if ($current['is_nullable'] != $expected['is_nullable']) {
-				if (!$current['is_nullable']) {
-					$toChange['null'] = '';
+				$needChange = true;
+				if ($current['is_nullable'] && !$expected['is_nullable']) {
+					throw new Exception("Field struct needs change 'is_nullable' to false but this change is not implemented - field '{$fldName}': " . json_encode(array('expected'=>$expected,'current'=>$current)));
 				}
 			}
-			$sqlChange = "alter table CRM_NOTIFY change {$fldName} {$fldName} {$toChange['type']} {$toChange['null']} {$toChange['default']}";
-			if ($needChange) {
-				DBG::_(true, true, "TODO: sqlChange", $sqlChange, __CLASS__, __FUNCTION__, __LINE__);
-			}
-		}
-		if(0)foreach ($struct as $col) {
-			$fldName = $col['Field'];
-			if (!array_key_exists($fldName, $expectedStruct)) continue;// skip not defined cols
-			$expected = $expectedStruct[$fldName];
-			$toChange = array();
-			$toChange['need_change'] = false;
-			$toChange['type'] = $col['Type'];
-			$toChange['null'] = ('YES' == $col['Null'])? '' : 'NOT NULL';
-			// TODO: cannot determine when default is NULL
-			$toChange['default'] = (!is_null($col['Default']))? "DEFAULT {$col['Default']}" : '';
-
-			$expectedNullable = (array_key_exists('nullable', $expected) && $expected['nullable']);
-			$colNullable = ('YES' == $col['Null']);
-			if ($expectedNullable != $colNullable) {
-				$toChange['null'] = ($expectedNullable)? '' : 'NOT NULL';
-				$toChange['need_change'] = true;
-			}
-
-			// examples:
-			//   1 - NOT NULL
-			//   2 - NOT NULL DEFAULT ''
-			// x 3 - NOT NULL DEFAULT NULL
-			// x 4 - NULL
-			//   5 - NULL DEFAULT ''
-			//   6 - NULL DEFAULT NULL
-			//       1 == 2, 4 -> 6
-			$expectedHasDefault = array_key_exists('default', $expected);
-			$expectedDefaultValue = (array_key_exists('default', $expected))? $expected['default'] : "''";
-			$colDefault = $col['Default'];
-			if ($expectedDefault != $colDefault) {
-				$toChange['default'] = 'DEFAULT ' . ((is_null($expectedDefault))? 'NULL' : $expectedDefault);
-				$toChange['need_change'] = true;
+			if ($current['max_length'] != $expected['max_length']) {
+				$needChange = true;
+				if ($current['max_length'] > $expected['max_length']) {
+					throw new Exception("Field struct needs decrease 'max_length' but this change is not implemented - field '{$fldName}': " . json_encode(array('expected'=>$expected,'current'=>$current)));
+				}
 			}
+			if ($current['default_value'] !== $expected['default_value']) $needChange = true;
 
-			// ALTER TABLE `CRM_NOTIFY` CHANGE `who` `who` VARCHAR(20) NOT NULL
-			// ALTER TABLE `CRM_NOTIFY` CHANGE `who` `who` VARCHAR(20) NULL
-			// ALTER TABLE `CRM_NOTIFY` CHANGE `who` `who` VARCHAR(20) NULL DEFAULT NULL
-			$sqlChange = "alter table CRM_NOTIFY change {$fldName} {$fldName} {$toChange['type']} {$toChange['null']} {$toChange['default']}";
-			if ($toChange['need_change']) {
-				DBG::_(true, true, "TODO: sqlChange", $sqlChange, __CLASS__, __FUNCTION__, __LINE__);
+			if ($needChange) {
+				DBG::_(true, true, "EXEC sqlChange ...", $sqlChange, __CLASS__, __FUNCTION__, __LINE__);
+				$this->exec($sqlChange);
 			}
 		}
 	}
@@ -323,87 +297,17 @@ class Core_Pdo extends PDO {
 		return $fixedStruct;
 	}
 
-	public function showCreateStructMysql($tblName, $struct, $params = array()) {
+	public function showCreateStruct($tblName, $struct, $params = array()) {
+		// TODO: check database type, if == MySQL - fix construct
 		$expectedStruct = $this->_fixExpectedStruct($struct);
 		$linesSql = array();
 		$dbgSql = array();
 		foreach ($expectedStruct as $fldName => $fld) {
-			$nullSql = ($fld['is_nullable'])? '' : 'NOT NULL';
-			$defaultSql = (is_null($fld['default_value']))? 'DEFAULT NULL' : "DEFAULT '{$fld['default_value']}'";
-			if (is_null($fld['default_value']) && !$fld['is_nullable']) $defaultSql = '';
-			switch ($fld['type']) {
-				case 'char':
-				case 'varchar': $linesSql[] = "`{$fldName}` {$fld['type']}({$fld['max_length']}) {$nullSql} {$defaultSql}"; break;
-				case 'text':
-				case 'tinytext':
-				case 'longtext':
-				case 'mediumtext': $linesSql[] = "`{$fldName}` {$fld['type']} {$nullSql}"; break;
-				case 'time':
-				case 'timestamp':
-				case 'year':
-				case 'date':
-				case 'datetime': $linesSql[] = "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}"; break;
-// 		-- Unimplemented type 'int': {"type":"int","is_nullable":false,"default_is_null":false,"default_value":null,"max_length":null,"num_precision":10,"num_scale":0,"char_encoding":null,"char_collation":null,"extra":null}
-//		-- Unimplemented type 'int': {"type":"int","num_precision":11,"default_value":null,"is_nullable":true,"default_is_null":false,"max_length":null,"num_scale":0,"char_encoding":null,"char_collation":null,"extra":null}
-				case 'int':
-				case 'tinyint':
-				case 'smallint':
-				case 'mediumint':
-				case 'bigint':
-					if ($fld['num_precision'] > 0) {
-						$typeParamsSql = "(" . ($fld['num_precision'] + 1) . ")";
-					}
-					//if ($fld['num_scale']) $typeParamsSql = ",{$fld['num_scale']}";
-					$linesSql[] = "`{$fldName}` {$fld['type']}{$typeParamsSql} {$nullSql} {$defaultSql}";
-					break;
-				case 'decimal':
-					$typeParamsSql = "{$fld['num_precision']},{$fld['num_scale']}";
-					$linesSql[] = "`{$fldName}` {$fld['type']}({$typeParamsSql}) {$nullSql} {$defaultSql}";
-					break;
-				case 'float':
-				case 'double':
-				case 'real':
-					$linesSql[] = "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
-					break;
-				case 'enum':
-				case 'set':
-					$typeParamsSql = "'" . implode("','", $fld['values']) . "'";
-					$linesSql[] = "`{$fldName}` {$fld['type']}({$typeParamsSql}) {$nullSql} {$defaultSql}";
-					break;
-				case 'bit':
-				case 'binary':
-				case 'varbinary':
-					$linesSql[] = "`{$fldName}` {$fld['type']}({$fld['max_length']}) {$nullSql} {$defaultSql}";
-					break;
-				case 'boolean':
-				case 'serial':
-				case 'blob':
-				case 'tinyblob':
-				case 'mediumblob':
-				case 'longblob':
-					$linesSql[] = "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
-					break;
-				case 'geometry':
-				case 'point':
-				case 'linestring':
-				case 'polygon':
-				case 'multipoint':
-				case 'multilinestring':
-				case 'multipolygon':
-				case 'geometrycollection':
-					$linesSql[] = "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
-					break;
-				case 'UNIQUE KEY':
-					$keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
-					$linesSql[] = "UNIQUE KEY `{$fldName}` ({$keyFieldsSql})";
-					break;
-				case 'KEY':
-					$keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
-					$linesSql[] = "KEY `{$fldName}` ({$keyFieldsSql})";
-					break;
-//			$expectedStruct['uniq_key_1'] = array('type'=>'UNIQUE KEY', 'key_fields'=>array('who','when','what'));// UNIQUE KEY `uniq_key_1` (`who`,`when`,`what`)
-//			$expectedStruct['key_who'] = array('type'=>'KEY', 'key_fields'=>array('who'));// KEY `key_who` (`who`)
-				default: $dbgSql[] = "-- Unimplemented type '{$fld['type']}': " . json_encode($fld);
+			$sqlFieldStruct = $this->showTableStructField($fldName, $fld);
+			if ($sqlFieldStruct) {
+				$linesSql[] = $sqlFieldStruct;
+			} else {
+				$dbgSql[] = "-- Unimplemented type '{$fld['type']}': " . json_encode($fld);
 			}
 		}
 		$linesSql = implode("\n\t\t, ", $linesSql);
@@ -418,6 +322,85 @@ EOF_STRUCT_MYSQL;
 		return $structSql;
 	}
 
+	public function showTableStructField($fldName, $fld) {
+		// TODO: check database type, if == MySQL - fix construct
+		$nullSql = ($fld['is_nullable'])? '' : 'NOT NULL';
+		$defaultSql = (is_null($fld['default_value']))? 'DEFAULT NULL' : "DEFAULT '{$fld['default_value']}'";
+		if (is_null($fld['default_value']) && !$fld['is_nullable']) $defaultSql = '';
+		switch ($fld['type']) {
+			case 'char':
+			case 'varchar': return "`{$fldName}` {$fld['type']}({$fld['max_length']}) {$nullSql} {$defaultSql}"; break;
+			case 'text':
+			case 'tinytext':
+			case 'longtext':
+			case 'mediumtext': return "`{$fldName}` {$fld['type']} {$nullSql}"; break;
+			case 'time':
+			case 'timestamp':
+			case 'year':
+			case 'date':
+			case 'datetime': return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}"; break;
+// 		-- Unimplemented type 'int': {"type":"int","is_nullable":false,"default_is_null":false,"default_value":null,"max_length":null,"num_precision":10,"num_scale":0,"char_encoding":null,"char_collation":null,"extra":null}
+//		-- Unimplemented type 'int': {"type":"int","num_precision":11,"default_value":null,"is_nullable":true,"default_is_null":false,"max_length":null,"num_scale":0,"char_encoding":null,"char_collation":null,"extra":null}
+			case 'int':
+			case 'tinyint':
+			case 'smallint':
+			case 'mediumint':
+			case 'bigint':
+				if ($fld['num_precision'] > 0) {
+					$typeParamsSql = "(" . ($fld['num_precision'] + 1) . ")";
+				}
+				//if ($fld['num_scale']) $typeParamsSql = ",{$fld['num_scale']}";
+				return "`{$fldName}` {$fld['type']}{$typeParamsSql} {$nullSql} {$defaultSql}";
+				break;
+			case 'decimal':
+				$typeParamsSql = "{$fld['num_precision']},{$fld['num_scale']}";
+				return "`{$fldName}` {$fld['type']}({$typeParamsSql}) {$nullSql} {$defaultSql}";
+				break;
+			case 'float':
+			case 'double':
+			case 'real':
+				return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
+				break;
+			case 'enum':
+			case 'set':
+				$typeParamsSql = "'" . implode("','", $fld['values']) . "'";
+				return "`{$fldName}` {$fld['type']}({$typeParamsSql}) {$nullSql} {$defaultSql}";
+				break;
+			case 'bit':
+			case 'binary':
+			case 'varbinary':
+				return "`{$fldName}` {$fld['type']}({$fld['max_length']}) {$nullSql} {$defaultSql}";
+				break;
+			case 'boolean':
+			case 'serial':
+			case 'blob':
+			case 'tinyblob':
+			case 'mediumblob':
+			case 'longblob':
+				return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
+				break;
+			case 'geometry':
+			case 'point':
+			case 'linestring':
+			case 'polygon':
+			case 'multipoint':
+			case 'multilinestring':
+			case 'multipolygon':
+			case 'geometrycollection':
+				return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
+				break;
+			case 'UNIQUE KEY':
+				$keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
+				return "UNIQUE KEY `{$fldName}` ({$keyFieldsSql})";
+				break;
+			case 'KEY':
+				$keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
+				return "KEY `{$fldName}` ({$keyFieldsSql})";
+				break;
+		}
+		return null;
+	}
+
 	public function fetchAll($sql) {
 		$sth = $this->prepare($sql);
 		$sth->execute();

+ 58 - 223
SE/se-lib/Route/Notify.php

@@ -35,7 +35,9 @@ class Route_Notify extends RouteBase {
 		try {
 			$this->reinstall();
 		} catch (Exception $e) {
+			echo'<div style="border:1px solid red; background:red; color:#fff;">';
 			echo "Error #" . $e->getCode() .  "|" . $e->getLine() .  ": " . $e->getMessage();
+			echo'</div>';
 		}
 		die('OK');
 	}
@@ -71,16 +73,8 @@ class Route_Notify extends RouteBase {
 		}
 	}
 
-	public function reinstall() {
-		$sqlList = array();
+	public function testReinstallTable() {
 		$pdo = DB::getPDO();
-		/*
-			`CRM_NOTIFY`:
-				- who - user login
-				- when - shedule (once a day, once on houd)
-				- what - action type
-				- last_exec_time
-		*/
 		if(1){// TEST
 			$expectedStruct = array();
 			$expectedStruct['who'] = array('type'=>'varchar', 'max_length'=>20, 'is_nullable'=>false);// `who` varchar(20) -- TODO:  NOT NULL
@@ -135,29 +129,15 @@ class Route_Notify extends RouteBase {
 			$expectedStruct['t_multipolygon'] = array('type'=>'multipolygon');
 			$expectedStruct['t_geometrycollection'] = array('type'=>'geometrycollection');
 
-			$sqlCreate = $pdo->showCreateStructMysql('CRM_NOTIFY', $expectedStruct, array('char_encoding'=>'latin2'));
+			$sqlCreate = $pdo->showCreateStruct('_test_table', $expectedStruct, array('char_encoding'=>'latin2'));
 			DBG::_(true, true, "fixedEpectedStruct", $sqlCreate, __CLASS__, __FUNCTION__, __LINE__);
-			$pdo->assertTableStruct('CRM_NOTIFY', $expectedStruct, array('char_encoding'=>'latin2'));
+			$pdo->assertTableStruct('_test_table', $expectedStruct, array('char_encoding'=>'latin2'));
 			{// force - drop/create
-				$pdo->exec("DROP TABLE IF EXISTS CRM_NOTIFY");
+				$pdo->exec("DROP TABLE IF EXISTS _test_table");
 				$pdo->exec($sqlCreate);
 			}
-			return;
 		}
-
-		$sth = $pdo->prepare("
-				CREATE TABLE IF NOT EXISTS CRM_NOTIFY (
-					`who` varchar(20) -- TODO:  NOT NULL
-					, `when` varchar(255) NOT NULL
-					, `what` varchar(255) NOT NULL
-					, `last_exec_time` datetime -- TODO: DEFAULT NULL
-					, `_created` datetime
-					, UNIQUE KEY `uniq` (`who`,`when`,`what`)
-					, KEY `who` (`who`)
-				) ENGINE=MyISAM DEFAULT CHARSET=latin2
-		");
-		$sth->execute();
-		if(1){// TEST nulls
+		if(0){// TEST nulls
 			$sth = $pdo->prepare("DROP TABLE IF EXISTS CRM_NOTIFY");
 			$sth->execute();
 			$sth = $pdo->prepare("
@@ -197,210 +177,65 @@ class Route_Notify extends RouteBase {
 			$expectedStruct['t1'] = array('type'=>'varchar', 'max_length'=>300, 'default_value'=>null);
 			//$expectedStruct['t2'] = array('type'=>'int', 'num_precision'=>11, 'default_value'=>null);
 			$pdo->assertTableStruct('CRM_NOTIFY', $expectedStruct);
-			return;
-		}
-
-		$struct = $pdo->getTableStruct('CRM_NOTIFY');
-		DBG::_(true, true, "struct", $struct, __CLASS__, __FUNCTION__, __LINE__);
-
-		$expectedStruct = array();
-		$expectedStruct['who'] = array('type'=>'varchar', 'max_length'=>20);
-		$expectedStruct['when'] = array('type'=>'varchar', 'max_length'=>255);
-		$expectedStruct['what'] = array('type'=>'varchar', 'max_length'=>255);
-		$expectedStruct['last_exec_time'] = array('type'=>'datetime');
-		$expectedStruct['_created'] = array('type'=>'datetime');
-		$expectedStruct['tX'] = array('type'=>'varchar', 'max_length'=>255, 'default_value'=>null, 'is_nullable'=>true);
-
-		DBG::_(true, true, "is_null('')=(".is_null('').") is_null()=(".is_null().") is_null(null)=(".is_null(null).")", null, __CLASS__, __FUNCTION__, __LINE__);
-
-		if(0){// keys
-			$sth = $pdo->prepare("show keys from CRM_NOTIFY");
-			$sth->execute();
-			$keys = $sth->fetchAll();
-			DBG::_(true, true, "keys", $keys, __CLASS__, __FUNCTION__, __LINE__);
 		}
-
-		//$this->reinstallFunctions();
 	}
 
-	public function getActiveMessagesForTable($tblName) {
-		if (empty($tblName)) return;
-		$db = DB::getDB();
-		$tblName = $db->_($tblName);
-
-		$usrLogin = User::getLogin();
-		$msgs = null;
-		$sql = "select m.*
-			from `CRM_UI_MSGS` m
-			where m.`uiTargetType`='default_db_table'
-				and m.`A_STATUS`='WAITING'
-				and m.`uiTargetName`='{$tblName}'
-				and (m.`userTargetType` in('everyone')
-					or (m.`userTargetType`='user' and m.`userTargetName`='{$usrLogin}')
-					-- TODO: use 'admin', 'group'
-				)
-		";
-		$db = DB::getDB();
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			if ($msg = $this->parseMessage($r)) {
-				$msg['link'] = 'index.php?_route=Msgs&_task=run&_msgId=' . $r->ID;
-				$msg['linkType'] = 'ajax';
-				$msgs[$r->ID] = $msg;
-			}
+	public function reinstall() {
+		$sqlList = array();
+		$pdo = DB::getPDO();
+		$sqlCreate = <<<EOF_SQL_CREATE
+			CREATE TABLE IF NOT EXISTS CRM_NOTIFY (
+				`who` varchar(20) NOT NULL
+				, `when` varchar(255) NOT NULL
+				, `what` varchar(255) NOT NULL
+				, `last_exec_time` datetime DEFAULT NULL
+				, `_created` datetime NOT NULL
+				, UNIQUE KEY `uniq_key_1` (`who`,`when`,`what`)
+				, KEY `key_who` (`who`)
+			) ENGINE=MyISAM DEFAULT CHARSET=latin2
+EOF_SQL_CREATE;
+		/*
+			`CRM_NOTIFY`:
+				- who - user login
+				- when - shedule (once a day, once on houd)
+				- what - action type
+				- last_exec_time
+		*/
+		if(1){
+			$pdo->exec("DROP TABLE IF EXISTS CRM_NOTIFY");
+			$pdo->exec("
+					CREATE TABLE IF NOT EXISTS CRM_NOTIFY (
+						`who` varchar(20) NOT NULL
+						, `when` varchar(255) NOT NULL
+						, `what` varchar(255) NOT NULL DEFAULT ''
+						, `last_exec_time` datetime
+						, KEY `who` (`who`)
+					) ENGINE=MyISAM DEFAULT CHARSET=latin2
+			");
 		}
-		return $msgs;
-	}
-
-	public function getActiveMessagesForTableRecord($tblName, $id) {
-		if (empty($tblName)) return;
-		$db = DB::getDB();
-		$tblName = $db->_($tblName);
+		{// assertTableStruct CRM_NOTIFY
+			$expectedStruct = array();
+			$expectedStruct['who'] = array('type'=>'varchar', 'max_length'=>20, 'is_nullable'=>false);// `who` varchar(20) -- TODO:  NOT NULL
+			$expectedStruct['when'] = array('type'=>'varchar', 'max_length'=>255, 'is_nullable'=>false);// `when` varchar(255) NOT NULL
+			$expectedStruct['what'] = array('type'=>'varchar', 'max_length'=>255, 'is_nullable'=>false);// `what` varchar(255) NOT NULL
+			$expectedStruct['last_exec_time'] = array('type'=>'datetime', 'is_nullable'=>true);// `last_exec_time` datetime
+			$expectedStruct['_created'] = array('type'=>'datetime', 'is_nullable'=>false);// `_created` datetime
+			$expectedStruct['uniq_key_1'] = array('type'=>'UNIQUE KEY', 'key_fields'=>array('who','when','what'));// UNIQUE KEY `uniq_key_1` (`who`,`when`,`what`)
+			$expectedStruct['key_who'] = array('type'=>'KEY', 'key_fields'=>array('who'));// KEY `key_who` (`who`)
 
-		$usrLogin = User::getLogin();
-		$msgs = null;
-		$sql = "select m.*
-			from `CRM_UI_MSGS` m
-			where m.`uiTargetType`='default_db_table_record'
-				and m.`uiTargetName`='{$tblName}.{$id}'
-				and (m.`userTargetType` in('everyone')
-					or (m.`userTargetType`='user' and m.`userTargetName`='{$usrLogin}')
-					-- TODO: use group id
-				)
-				and m.`A_STATUS`='WAITING'
-			order by m.`ID` DESC
-		";
-		$db = DB::getDB();
-		$res = $db->query($sql);
-		while ($r = $db->fetch($res)) {
-			if ($msg = $this->parseMessage($r)) {
-				$msg['link'] = 'index.php?_route=Msgs&_task=run&_msgId=' . $r->ID;
-				$msg['linkType'] = 'ajax';
-				$msgs[$r->ID] = $msg;
+			DBG::_(true, true, "sqlCreate - raw", $sqlCreate, __CLASS__, __FUNCTION__, __LINE__);
+			$sqlCreate = $pdo->showCreateStruct('CRM_NOTIFY', $expectedStruct, array('char_encoding'=>'latin2'));
+			DBG::_(true, true, "sqlCreate - generated", $sqlCreate, __CLASS__, __FUNCTION__, __LINE__);
+			$pdo->assertTableStruct('CRM_NOTIFY', $expectedStruct, array('char_encoding'=>'latin2'));
+			if(0){// force - drop/create
+				$pdo->exec("DROP TABLE IF EXISTS CRM_NOTIFY");
+				$pdo->exec($sqlCreate);
 			}
 		}
-		return $msgs;
-	}
-
-	public function parseMessage($r) {
-		$msg = null;// ['type'=>'info', 'message'=>'...']
-		// $r->app_className - for automatic msgs to search for msg text
-		// $r->msg - for automatic msgs to search for msg text
-		// $r->msgType - 'info','danger','warning','success'
-		if (!empty($r->app_className)) {
-			$route = Router::getRoute($r->app_className);
-			$msg = array();
-			$msg['message'] = $route->parseMessageFromMsgsSystem($r->msg);
-			$msg['type'] = $r->msgType;
-			$msg['_raw'] = $r;
-		} else {
-			$msg = array();
-			$msg['message'] = $this->parseMessageFromMsgsSystem($r->msg);
-			$msg['type'] = $r->msgType;
-			$msg['_raw'] = $r;
-		}
-		return $msg;
-	}
-
-	public function parseMessageFromMsgsSystem($msg) {
-		return $msg;
-	}
-
-	public function runByMessageId($id) {
-		$msgRow = $this->getActiveMessage($id);
-		$execNotes = '';
-		if (!empty($msgRow->app_className)) {
-			$route = Router::getRoute($msgRow->app_className);
-			$route->runByMessageFromMsgsSystem($msgRow->msg, $execNotes);
-		}
-		$this->forceFinishMessage($id, $execNotes);
-	}
-
-	public function getMessage($id) {
-		if (empty($id)) return;
-		$id = intval($id);
-		if ($id <= 0) return;
-
-		$msg = null;
-		$sql = "select * from `CRM_UI_MSGS` where `ID`='{$id}' ";
-		$db = DB::getDB();
-		$res = $db->query($sql);
-		if ($r = $db->fetch($res)) {
-			$msg = $r;
-		}
-		return $msg;
-	}
-
-	public function getActiveMessage($id) {
-		if (empty($id)) return;
-		$id = intval($id);
-		if ($id <= 0) return;
-
-		$msg = null;
-		$sql = "select m.*
-			from `CRM_UI_MSGS` m
-			where m.`ID`='{$id}'
-				and m.`A_STATUS`='WAITING'
-		";
-		$db = DB::getDB();
-		$res = $db->query($sql);
-		if ($r = $db->fetch($res)) {
-			$msg = $r;
-		}
-		if (!$msg) {
-			throw new HttpException("Message not found", 404);
+		{// assertTableStruct CRM_NOTIFY_EXEC_LOG
+			// TODO: create table CRM_NOTIFY_EXEC_LOG with action log
 		}
-		return $msg;
-	}
-
-	public function forceFinishMessage($id, $execNotes) {
-		if (empty($id)) return;
-		$id = intval($id);
-		if ($id <= 0) return;
-
-		$usrLogin = User::getLogin();
-		$db = DB::getDB();
-		$execNotes = $db->_($execNotes);
-		$sql = "update `CRM_UI_MSGS`
-			set `A_STATUS`='OFF_HARD'
-				, `actionExecutedTime`=NOW()
-				, `actionNotes`='{$execNotes}'
-				, `A_RECORD_UPDATE_DATE`=NOW()
-				, `A_RECORD_UPDATE_AUTHOR`='{$usrLogin}'
-			where `ID`='{$id}'
-		";
-		$db->query($sql);
-		return;
-	}
-
-	public function removeMessage($id) {
-		if (empty($id)) return;
-		$id = intval($id);
-		if ($id <= 0) return;
-
-		$sql = "update `CRM_UI_MSGS` set `A_STATUS`='DELETED' where `ID`='{$id}' ";
-		$db = DB::getDB();
-		$db->query($sql);
-	}
-
-	public function removeTableRecordMsg($idMsg) {
-		// IDEA: do kosza - add trigger to insert into `CRM_UI_MSGS__TRASH` after DELETE on `CRM_UI_MSGS`
-		$idMsg = intval($idMsg);
-		if ($idMsg <= 0) throw new Exception("Brak wiadomości!");
-		$usrLogin = User::getLogin();
-		$db = DB::getDB();
-		if (!$db) throw new Exception("Brak dazy danych!");
-		if ($db->has_errors()) throw new Exception("DB Errors: " . implode("\n<br>", $db->get_errors()));
-		$sqlTODO = "delete `CRM_UI_MSGS` where `ID`='{$idMsg}' ";
-		$sql = "update `CRM_UI_MSGS`
-			set `A_STATUS`=IF('{$usrLogin}'=`A_RECORD_CREATE_AUTHOR`, 'DELETED', 'OFF_SOFT')
-				, `A_RECORD_DELETE_AUTHOR`='{$usrLogin}'
-				, `A_RECORD_DELETE_DATE`=NOW()
-			where `ID`='{$idMsg}'
-		";
-		DBG::_('DBG_MSGS', '>1', "sql", $sql, __CLASS__, __FUNCTION__, __LINE__);
-		$res = $db->query($sql);
-		if (!$res || $db->has_errors()) throw new Exception("Wystąpiły błędy podczas próby zapisu wiadomości: " . implode("\n<br>", $db->get_errors()));
+		//$this->reinstallFunctions();
 	}
 
 }