PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/consumer/include/flexcdc.php

http://flexviews.googlecode.com/
PHP | 1280 lines | 1248 code | 10 blank | 22 comment | 9 complexity | be9e9c2333c3640908bf01253a8846f0 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0
  1. <?php
  2. /* FlexCDC is part of Flexviews for MySQL
  3. Copyright 2008-2010 Justin Swanhart
  4. FlexViews is free software: you can redistribute it and/or modify
  5. it under the terms of the Lesser GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. FlexViews is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with FlexViews in the file COPYING, and the Lesser extension to
  14. the GPL (the LGPL) in COPYING.LESSER.
  15. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. error_reporting(E_ALL);
  18. ini_set('memory_limit', 1024 * 1024 * 1024);
  19. define('SOURCE', 'source');
  20. define('DEST', 'dest');
  21. /*
  22. The exit/die() functions normally exit with error code 0 when a string is passed in.
  23. We want to exit with error code 1 when a string is passed in.
  24. */
  25. function die1($error = 1,$error2=1) {
  26. if(is_string($error)) {
  27. echo1($error . "\n");
  28. exit($error2);
  29. } else {
  30. exit($error);
  31. }
  32. }
  33. function echo1($message) {
  34. global $ERROR_FILE;
  35. fputs(isset($ERROR_FILE) && is_resource($ERROR_FILE) ? $ERROR_FILE : STDERR, $message);
  36. }
  37. function my_mysql_query($a, $b=NULL, $debug=false) {
  38. if($debug) echo "$a\n";
  39. if($b) {
  40. $r = mysql_query($a, $b);
  41. } else {
  42. $r = mysql_query($a);
  43. }
  44. if(!$r) {
  45. echo1("SQL_ERROR IN STATEMENT:\n$a\n");
  46. if($debug) {
  47. $pr = mysql_error();
  48. echo1(print_r(debug_backtrace(),true));
  49. echo1($pr);
  50. }
  51. }
  52. return $r;
  53. }
  54. class FlexCDC {
  55. static function concat() {
  56. $result = "";
  57. for ($i = 0;$i < func_num_args();$i++) {
  58. $result .= func_get_arg($i);
  59. }
  60. return $result;
  61. }
  62. static function split_sql($sql) {
  63. $regex=<<<EOREGEX
  64. /
  65. |(\(.*?\)) # Match FUNCTION(...) OR BAREWORDS
  66. |("[^"](?:|\"|"")*?"+)
  67. |('[^'](?:|\'|'')*?'+)
  68. |(`(?:[^`]|``)*`+)
  69. |([^ ,]+)
  70. /x
  71. EOREGEX
  72. ;
  73. $tokens = preg_split($regex, $sql,-1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
  74. return $tokens;
  75. }
  76. # Settings to enable bulk import
  77. protected $inserts = array();
  78. protected $deletes = array();
  79. protected $bulk_insert = true;
  80. protected $mvlogDB = NULL;
  81. public $mvlogList = array();
  82. protected $activeDB = NULL;
  83. protected $onlyDatabases = array();
  84. protected $cmdLine;
  85. protected $tables = array();
  86. protected $mvlogs = 'mvlogs';
  87. protected $binlog_consumer_status = 'binlog_consumer_status';
  88. protected $mview_uow = 'mview_uow';
  89. protected $source = NULL;
  90. protected $dest = NULL;
  91. protected $serverId = NULL;
  92. protected $binlogServerId=1;
  93. protected $gsn_hwm;
  94. public $raiseWarnings = false;
  95. public $delimiter = ';';
  96. protected $log_retention_interval = "10 day";
  97. public function get_source($new = false) {
  98. if($new) return $this->new_connection(SOURCE);
  99. return $this->source;
  100. }
  101. public function get_dest($new = false) {
  102. if($new) return $this->new_connection(DEST);
  103. return $this->dest;
  104. }
  105. function new_connection($connection_type) {
  106. $S = $this->settings['source'];
  107. $D = $this->settings['dest'];
  108. switch($connection_type) {
  109. case 'source':
  110. /*TODO: support unix domain sockets */
  111. $handle = mysql_connect($S['host'] . ':' . $S['port'], $S['user'], $S['password'], true) or die1('Could not connect to MySQL server:' . mysql_error());
  112. return $handle;
  113. case 'dest':
  114. $handle = mysql_connect($D['host'] . ':' . $D['port'], $D['user'], $D['password'], true) or die1('Could not connect to MySQL server:' . mysql_error());
  115. return $handle;
  116. }
  117. return false;
  118. }
  119. #Construct a new consumer object.
  120. #By default read settings from the INI file unless they are passed
  121. #into the constructor
  122. public function __construct($settings = NULL, $no_connect = false) {
  123. if(!$settings) {
  124. $settings = $this->read_settings();
  125. $this->settings = $settings;
  126. }
  127. if(!$this->cmdLine) $this->cmdLine = `which mysqlbinlog`;
  128. if(!$this->cmdLine) {
  129. die1("could not find mysqlbinlog!",2);
  130. }
  131. #only record changelogs from certain databases?
  132. if(!empty($settings['flexcdc']['only_database'])) {
  133. $vals = explode(',', $settings['flexcdc']['only_databases']);
  134. foreach($vals as $val) {
  135. $this->onlyDatabases[] = trim($val);
  136. }
  137. }
  138. if(!empty($settings['flexcdc']['mvlogs'])) $this->mvlogs=$settings['flexcdc']['mvlogs'];
  139. if(!empty($settings['flexcdc']['binlog_consumer_status'])) $this->binlog_consumer_status=$settings['flexcdc']['binlog_consumer_status'];
  140. if(!empty($settings['flexcdc']['mview_uow'])) $this->mview_uow=$settings['flexcdc']['mview_uow'];
  141. if(!empty($settings['flexcdc']['log_retention_interval'])) $this->log_retention_interval=$settings['flexcdc']['log_retention_interval'];
  142. #the mysqlbinlog command line location may be set in the settings
  143. #we will autodetect the location if it is not specified explicitly
  144. if(!empty($settings['flexcdc']['mysqlbinlog'])) {
  145. $this->cmdLine = $settings['flexcdc']['mysqlbinlog'];
  146. }
  147. #build the command line from user, host, password, socket options in the ini file in the [source] section
  148. foreach($settings['source'] as $k => $v) {
  149. $this->cmdLine .= " --$k=$v";
  150. }
  151. #database into which to write mvlogs
  152. $this->mvlogDB = $settings['flexcdc']['database'];
  153. $this->auto_changelog = $settings['flexcdc']['auto_changelog'];
  154. #shortcuts
  155. if(!empty($settings['raise_warnings']) && $settings['raise_warnings'] != 'false') {
  156. $this->raiseWarnings=true;
  157. }
  158. if(!empty($settings['flexcdc']['bulk_insert']) && $settings['flexcdc']['bulk_insert'] != 'false') {
  159. $this->bulk_insert = true;
  160. }
  161. if(!$no_connect) {
  162. $this->source = $this->get_source(true);
  163. $this->dest = $this->get_dest(true);
  164. }
  165. $this->settings = $settings;
  166. }
  167. protected function initialize() {
  168. if($this->source === false) $this->source = $this->get_source(true);
  169. if($this->dest === false) $this->dest = $this->get_dest(true);
  170. $this->initialize_dest();
  171. $this->get_source_logs();
  172. $this->cleanup_logs();
  173. }
  174. public function table_exists($schema, $table) {
  175. $sql = "select 1 from information_schema.tables where table_schema = '$schema' and table_name='$table' limit 1";
  176. $stmt = @my_mysql_query($sql, $this->dest);
  177. if(!$stmt) return false;
  178. if(mysql_fetch_array($stmt) !== false) {
  179. mysql_free_result($stmt);
  180. return true;
  181. }
  182. return false;
  183. }
  184. public function table_ordinal_datatype($schema,$table,$pos) {
  185. static $cache;
  186. $key = $schema . $table . $pos;
  187. if(!empty($cache[$key])) {
  188. return $cache[$key];
  189. }
  190. $log_name = $schema . '_' . $table;
  191. $table = mysql_real_escape_string($table, $this->dest);
  192. $pos = mysql_real_escape_string($pos);
  193. $sql = 'select data_type from information_schema.columns where table_schema="%s" and table_name="%s" and ordinal_position="%s"';
  194. $sql = sprintf($sql, $this->mvlogDB, $log_name, $pos+4);
  195. $stmt = my_mysql_query($sql, $this->dest);
  196. if($row = mysql_fetch_array($stmt) ) {
  197. $cache[$key] = $row[0];
  198. return($row[0]);
  199. }
  200. return false;
  201. }
  202. public function table_ordinal_is_unsigned($schema,$table,$pos) {
  203. /* NOTE: we look at the LOG table to see the structure, because it might be different from the source if the consumer is behind and an alter has happened on the source*/
  204. static $cache;
  205. $key = $schema . $table . $pos;
  206. if(!empty($cache[$key])) {
  207. return $cache[$key];
  208. }
  209. $log_name = $schema . '_' . $table;
  210. $table = mysql_real_escape_string($table, $this->dest);
  211. $pos = mysql_real_escape_string($pos);
  212. $sql = 'select column_type like "%%unsigned%%" is_unsigned from information_schema.columns where table_schema="%s" and table_name="%s" and ordinal_position=%d';
  213. $sql = sprintf($sql, $this->mvlogDB, $log_name, $pos+4);
  214. $stmt = my_mysql_query($sql, $this->dest);
  215. if($row = mysql_fetch_array($stmt) ) {
  216. $cache[$key] = $row[0];
  217. return($row[0]);
  218. }
  219. return false;
  220. }
  221. public function setup($force=false , $only_table=false) {
  222. $sql = "SELECT @@server_id";
  223. $stmt = my_mysql_query($sql, $this->source);
  224. $row = mysql_fetch_array($stmt);
  225. $this->serverId = $row[0];
  226. if(!mysql_select_db($this->mvlogDB,$this->dest)) {
  227. my_mysql_query('CREATE DATABASE ' . $this->mvlogDB) or die1('Could not CREATE DATABASE ' . $this->mvlogDB . "\n");
  228. mysql_select_db($this->mvlogDB,$this->dest);
  229. }
  230. if($only_table === false || $only_table == 'mvlogs') {
  231. if($this->table_exists($this->mvlogDB, $this->mvlogs, $this->dest)) {
  232. if(!$force) {
  233. trigger_error('Table already exists:' . $this->mvlogs . '. Setup aborted! (use --force to ignore this error)' , E_USER_ERROR);
  234. return false;
  235. }
  236. my_mysql_query('DROP TABLE `' . $this->mvlogDB . '`.`' . $this->mvlogs . '`;') or die1('COULD NOT DROP TABLE: ' . $this->mvlogs . "\n" . mysql_error() . "\n");
  237. }
  238. my_mysql_query("CREATE TABLE
  239. `" . $this->mvlogs . "` (table_schema varchar(50),
  240. table_name varchar(50),
  241. mvlog_name varchar(50),
  242. active_flag boolean default true,
  243. primary key(table_schema,table_name),
  244. unique key(mvlog_name)
  245. ) ENGINE=INNODB DEFAULT CHARSET=utf8;"
  246. , $this->dest) or die1('COULD NOT CREATE TABLE ' . $this->mvlogs . ': ' . mysql_error($this->dest) . "\n");
  247. }
  248. if($only_table === false || $only_table == 'mview_uow') {
  249. if(FlexCDC::table_exists($this->mvlogDB, $this->mview_uow, $this->dest)) {
  250. if(!$force) {
  251. trigger_error('Table already exists:' . $this->mview_uow . '. Setup aborted!' , E_USER_ERROR);
  252. return false;
  253. }
  254. my_mysql_query('DROP TABLE `' . $this->mvlogDB . '`.`' . $this->mview_uow . '`;') or die1('COULD NOT DROP TABLE: ' . $this->mview_uow . "\n" . mysql_error() . "\n");
  255. }
  256. my_mysql_query("CREATE TABLE
  257. `" . $this->mview_uow . "` (
  258. `uow_id` BIGINT AUTO_INCREMENT,
  259. `commit_time` DATETIME,
  260. `gsn_hwm` bigint NOT NULL DEFAULT 1,
  261. PRIMARY KEY(`uow_id`),
  262. KEY `commit_time` (`commit_time`)
  263. ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
  264. , $this->dest) or die1('COULD NOT CREATE TABLE ' . $this->mview_uow . ': ' . mysql_error($this->dest) . "\n");
  265. my_mysql_query("INSERT INTO `" . $this->mview_uow . "` VALUES (1, NULL, 1);", $this->dest) or die1('COULD NOT INSERT INTO:' . $this->mview_uow . "\n");
  266. }
  267. if($only_table === false || $only_table == 'binlog_consumer_status') {
  268. if(FlexCDC::table_exists($this->mvlogDB, $this->binlog_consumer_status, $this->dest)) {
  269. if(!$force) {
  270. trigger_error('Table already exists:' . $this->binlog_consumer_status .' Setup aborted!' , E_USER_ERROR);
  271. return false;
  272. }
  273. my_mysql_query('DROP TABLE `' . $this->mvlogDB . '`.`' . $this->binlog_consumer_status . '`;') or die1('COULD NOT DROP TABLE: ' . $this->binlog_consumer_status . "\n" . mysql_error() . "\n");
  274. }
  275. my_mysql_query("CREATE TABLE
  276. `" . $this->binlog_consumer_status . "` (
  277. `server_id` int not null,
  278. `master_log_file` varchar(100) NOT NULL DEFAULT '',
  279. `master_log_size` int(11) DEFAULT NULL,
  280. `exec_master_log_pos` int(11) default null,
  281. PRIMARY KEY (`server_id`, `master_log_file`)
  282. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
  283. , $this->dest) or die1('COULD NOT CREATE TABLE ' . $this->binlog_consumer_status . ': ' . mysql_error($this->dest) . "\n");
  284. #find the current master position
  285. $stmt = my_mysql_query('FLUSH TABLES WITH READ LOCK', $this->source) or die1(mysql_error($this->source));
  286. $stmt = my_mysql_query('SHOW MASTER STATUS', $this->source) or die1(mysql_error($this->source));
  287. $row = mysql_fetch_assoc($stmt);
  288. $stmt = my_mysql_query('UNLOCK TABLES', $this->source) or die1(mysql_error($this->source));
  289. $this->initialize();
  290. my_mysql_query("COMMIT;", $this->dest);
  291. $sql = "UPDATE `" . $this->binlog_consumer_status . "` bcs
  292. set exec_master_log_pos = master_log_size
  293. where server_id={$this->serverId}
  294. AND master_log_file < '{$row['File']}'";
  295. $stmt = my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error($this->dest) . "\n");
  296. $sql = "UPDATE `" . $this->binlog_consumer_status . "` bcs
  297. set exec_master_log_pos = {$row['Position']}
  298. where server_id={$this->serverId}
  299. AND master_log_file = '{$row['File']}'";
  300. $stmt = my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error($this->dest) . "\n");
  301. }
  302. my_mysql_query("commit;", $this->dest);
  303. return true;
  304. }
  305. #Capture changes from the source into the dest
  306. public function capture_changes($iterations=1) {
  307. $this->initialize();
  308. $count=0;
  309. $sleep_time=0;
  310. while($iterations <= 0 || ($iterations >0 && $count < $iterations)) {
  311. $this->initialize();
  312. #retrieve the list of logs which have not been fully processed
  313. #there won't be any logs if we just initialized the consumer above
  314. $sql = "SELECT bcs.*
  315. FROM `" . $this->mvlogDB . "`.`" . $this->binlog_consumer_status . "` bcs
  316. WHERE server_id=" . $this->serverId .
  317. " AND exec_master_log_pos < master_log_size
  318. ORDER BY master_log_file;";
  319. #echo " -- Finding binary logs to process\n";
  320. $stmt = my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error() . "\n");
  321. $processedLogs = 0;
  322. while($row = mysql_fetch_assoc($stmt)) {
  323. ++$processedLogs;
  324. $this->delimiter = ';';
  325. if ($row['exec_master_log_pos'] < 4) $row['exec_master_log_pos'] = 4;
  326. $execCmdLine = sprintf("%s --base64-output=decode-rows -v -R --start-position=%d --stop-position=%d %s", $this->cmdLine, $row['exec_master_log_pos'], $row['master_log_size'], $row['master_log_file']);
  327. $execCmdLine .= " 2>&1";
  328. echo "-- $execCmdLine\n";
  329. $proc = popen($execCmdLine, "r");
  330. if(!$proc) {
  331. die1('Could not read binary log using mysqlbinlog\n');
  332. }
  333. $line = fgets($proc);
  334. if(preg_match('%/mysqlbinlog:|^ERROR:%', $line)) {
  335. die1('Could not read binary log: ' . $line . "\n");
  336. }
  337. $this->binlogPosition = $row['exec_master_log_pos'];
  338. $this->logName = $row['master_log_file'];
  339. $this->process_binlog($proc, $row['master_log_file'], $row['exec_master_log_pos'],$line);
  340. $this->set_capture_pos();
  341. my_mysql_query('commit', $this->dest);
  342. pclose($proc);
  343. }
  344. if($processedLogs) ++$count;
  345. #we back off further each time up to maximum
  346. if(!empty($this->settings['flexcdc']['sleep_increment']) && !empty($this->settings['flexcdc']['sleep_maximum'])) {
  347. if($processedLogs) {
  348. $sleep_time=0;
  349. } else {
  350. $sleep_time += $this->settings['flexcdc']['sleep_increment'];
  351. $sleep_time = $sleep_time > $this->settings['flexcdc']['sleep_maximum'] ? $this->settings['flexcdc']['sleep_maximum'] : $sleep_time;
  352. #echo1('sleeping:' . $sleep_time . "\n");
  353. sleep($sleep_time);
  354. }
  355. }
  356. }
  357. return $processedLogs;
  358. }
  359. protected function read_settings() {
  360. if(!empty($argv[1])) {
  361. $iniFile = $argv[1];
  362. } else {
  363. $iniFile = "consumer.ini";
  364. }
  365. $settings=@parse_ini_file($iniFile,true) or die1("Could not read ini file: $iniFile\n");
  366. if(!$settings || empty($settings['flexcdc'])) {
  367. die1("Could not find [flexcdc] section or .ini file not found");
  368. }
  369. return $settings;
  370. }
  371. protected function refresh_mvlog_cache() {
  372. $this->mvlogList = array();
  373. $sql = "SELECT table_schema, table_name, mvlog_name from `" . $this->mvlogs . "` where active_flag=1";
  374. $stmt = my_mysql_query($sql, $this->dest);
  375. while($row = mysql_fetch_array($stmt)) {
  376. $this->mvlogList[$row[0] . $row[1]] = $row[2];
  377. }
  378. }
  379. /* Set up the destination connection */
  380. function initialize_dest() {
  381. #my_mysql_query("SELECT GET_LOCK('flexcdc::SOURCE_LOCK::" . $this->server_id . "',15)") or die1("COULD NOT OBTAIN LOCK\n");
  382. mysql_select_db($this->mvlogDB) or die1('COULD NOT CHANGE DATABASE TO:' . $this->mvlogDB . "\n");
  383. my_mysql_query("commit;", $this->dest);
  384. $stmt = my_mysql_query("SET SQL_MODE=STRICT_ALL_TABLES");
  385. $stmt = my_mysql_query("SET SQL_LOG_BIN=0", $this->dest);
  386. if(!$stmt) die1(mysql_error());
  387. my_mysql_query("BEGIN;", $this->dest) or die1(mysql_error());
  388. $stmt = my_mysql_query("select @@max_allowed_packet", $this->dest);
  389. $row = mysql_fetch_array($stmt);
  390. $this->max_allowed_packet = $row[0];
  391. $stmt = my_mysql_query("select gsn_hwm from {$this->mvlogDB}.{$this->mview_uow} order by uow_id desc limit 1",$this->dest)
  392. or die('COULD NOT GET GSN_HWM:' . mysql_error($this->dest) . "\n");
  393. $row = mysql_fetch_array($stmt);
  394. $this->gsn_hwm = $row[0];
  395. #echo1("Max_allowed_packet: " . $this->max_allowed_packet . "\n");
  396. }
  397. /* Get the list of logs from the source and place them into a temporary table on the dest*/
  398. function get_source_logs() {
  399. /* This server id is not related to the server_id in the log. It refers to the ID of the
  400. * machine we are reading logs from.
  401. */
  402. $sql = "SELECT @@server_id";
  403. $stmt = my_mysql_query($sql, $this->source);
  404. $row = mysql_fetch_array($stmt) or die1($sql . "\n" . mysql_error() . "\n");
  405. $this->serverId = $row[0];
  406. $sql = "select @@binlog_format";
  407. $stmt = my_mysql_query($sql, $this->source);
  408. $row = mysql_fetch_array($stmt) or die1($sql . "\n" . mysql_error() . "\n");
  409. if($row[0] != 'ROW') {
  410. die1("Exiting due to error: FlexCDC REQUIRES that the source database be using ROW binlog_format!\n");
  411. }
  412. $stmt = my_mysql_query("SHOW BINARY LOGS", $this->source);
  413. if(!$stmt) die1(mysql_error());
  414. $has_logs = false;
  415. while($row = mysql_fetch_array($stmt)) {
  416. if(!$has_logs) {
  417. my_mysql_query("CREATE TEMPORARY table log_list (log_name char(50), primary key(log_name))",$this->dest) or die1(mysql_error());
  418. $has_logs = true;
  419. }
  420. $sql = sprintf("INSERT INTO `" . $this->binlog_consumer_status . "` (server_id, master_log_file, master_log_size, exec_master_log_pos) values (%d, '%s', %d, 0) ON DUPLICATE KEY UPDATE master_log_size = %d ;", $this->serverId,$row['Log_name'], $row['File_size'], $row['File_size']);
  421. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error() . "\n");
  422. $sql = sprintf("INSERT INTO log_list (log_name) values ('%s')", $row['Log_name']);
  423. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error() . "\n");
  424. }
  425. }
  426. /* Remove any logs that have gone away */
  427. function cleanup_logs() {
  428. $sql = "DELETE bcs.* FROM `" . $this->binlog_consumer_status . "` bcs where exec_master_log_pos >= master_log_size and server_id={$this->serverId} AND master_log_file not in (select log_name from log_list)";
  429. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error() . "\n");
  430. $sql = "DROP TEMPORARY table IF EXISTS log_list";
  431. my_mysql_query($sql, $this->dest) or die1("Could not drop TEMPORARY TABLE log_list\n");
  432. }
  433. function purge_table_change_history() {
  434. $conn = $this->get_dest(true);
  435. $stmt = my_mysql_query("SET SQL_LOG_BIN=0", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  436. $sql = "select max(uow_id) from {$this->mvlogDB}.{$this->mview_uow} where commit_time <= NOW() - INTERVAL " . $this->log_retention_interval;
  437. $stmt = my_mysql_query($sql, $conn) or die1($sql . "\n" . mysql_error() . "\n");
  438. $row = mysql_fetch_array($stmt);
  439. $uow_id = $row[0];
  440. if(!trim($uow_id)) return true;
  441. $sql = "select min(uow_id) from {$this->mvlogDB}.{$this->mview_uow} where uow_id > {$uow_id}";
  442. $stmt = my_mysql_query($sql, $conn) or die1($sql . "\n" . mysql_error() . "\n");
  443. $row = mysql_fetch_array($stmt);
  444. $next_uow_id = $row[0];
  445. if(!trim($next_uow_id)) $uow_id = $uow_id - 1; /* don't purge the last row to avoid losing the gsn_hwm */
  446. $sql = "select concat('`','{$this->mvlogDB}', '`.`', mvlog_name,'`') mvlog_fqn from {$this->mvlogDB}.{$this->mvlogs} where active_flag = 1";
  447. $stmt = my_mysql_query($sql, $conn) or die1($sql . "\n" . mysql_error() . "\n");
  448. $done=false;
  449. $iterator = 0;
  450. /* Delete from each table in small 5000 row chunks, commit every 50000 */
  451. while($row = mysql_fetch_array($stmt)) {
  452. my_mysql_query("START TRANSACTION", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  453. while(!$done) {
  454. ++$iterator;
  455. if($iterator % 10 === 0) {
  456. my_mysql_query("COMMIT", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  457. my_mysql_query("START TRANSACTION", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  458. }
  459. $sql = "DELETE FROM {$row[0]} where uow_id <= {$uow_id} LIMIT 5000";
  460. my_mysql_query($sql, $conn) or die1($sql . "\n" . mysql_error() . "\n");
  461. if(mysql_affected_rows($conn)===0) $done=true;
  462. }
  463. my_mysql_query("COMMIT", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  464. }
  465. my_mysql_query("START TRANSACTION", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  466. $sql = "DELETE FROM {$this->mvlogDB}.{$this->mview_uow} where uow_id <= {$uow_id} LIMIT 5000";
  467. my_mysql_query($sql, $conn) or die1($sql . "\n" . mysql_error() . "\n");
  468. my_mysql_query("COMMIT", $conn) or die1($sql . "\n" . mysql_error() . "\n");
  469. }
  470. /* Update the binlog_consumer_status table to indicate where we have executed to. */
  471. function set_capture_pos() {
  472. $sql = sprintf("UPDATE `" . $this->mvlogDB . "`.`" . $this->binlog_consumer_status . "` set exec_master_log_pos = %d where master_log_file = '%s' and server_id = %d", $this->binlogPosition, $this->logName, $this->serverId);
  473. my_mysql_query($sql, $this->dest) or die1("COULD NOT EXEC:\n$sql\n" . mysql_error($this->dest));
  474. }
  475. /* Called when a new transaction starts*/
  476. function start_transaction() {
  477. my_mysql_query("START TRANSACTION", $this->dest) or die1("COULD NOT START TRANSACTION;\n" . mysql_error());
  478. $this->set_capture_pos();
  479. $sql = sprintf("INSERT INTO `" . $this->mview_uow . "` values(NULL,str_to_date('%s', '%%y%%m%%d %%H:%%i:%%s'),%d);",rtrim($this->timeStamp),$this->gsn_hwm);
  480. my_mysql_query($sql,$this->dest) or die1("COULD NOT CREATE NEW UNIT OF WORK:\n$sql\n" . mysql_error());
  481. $sql = "SET @fv_uow_id := LAST_INSERT_ID();";
  482. my_mysql_query($sql, $this->dest) or die1("COULD NOT EXEC:\n$sql\n" . mysql_error($this->dest));
  483. }
  484. /* Called when a transaction commits */
  485. function commit_transaction() {
  486. //Handle bulk insertion of changes
  487. if(!empty($this->inserts) || !empty($this->deletes)) {
  488. $this->process_rows();
  489. }
  490. $this->inserts = $this->deletes = $this->tables = array();
  491. $this->set_capture_pos();
  492. $sql = "UPDATE `{$this->mvlogDB}`.`{$this->mview_uow}` SET `commit_time`=str_to_date('%s','%%y%%m%%d %%H:%%i:%%s'), `gsn_hwm` = %d WHERE `uow_id` = @fv_uow_id";
  493. $sql = sprintf($sql, rtrim($this->timeStamp),$this->gsn_hwm);
  494. my_mysql_query($sql, $this->dest) or die('COULD NOT UPDATE ' . $this->mvlogDB . "." . $this->mview_uow . ':' . mysql_error($this->dest) . "\n");
  495. my_mysql_query("COMMIT", $this->dest) or die1("COULD NOT COMMIT TRANSACTION;\n" . mysql_error());
  496. }
  497. /* Called when a transaction rolls back */
  498. function rollback_transaction() {
  499. $this->inserts = $this->deletes = $this->tables = array();
  500. my_mysql_query("ROLLBACK", $this->dest) or die1("COULD NOT ROLLBACK TRANSACTION;\n" . mysql_error());
  501. #update the capture position and commit, because we don't want to keep reading a truncated log
  502. $this->set_capture_pos();
  503. my_mysql_query("COMMIT", $this->dest) or die1("COULD NOT COMMIT TRANSACTION LOG POSITION UPDATE;\n" . mysql_error());
  504. }
  505. /* Called when a row is deleted, or for the old image of an UPDATE */
  506. function delete_row() {
  507. $this->gsn_hwm+=1;
  508. $key = '`' . $this->mvlogDB . '`.`' . $this->mvlog_table . '`';
  509. $this->tables[$key]=array('schema'=>$this->db ,'table'=>$this->base_table);
  510. if ( $this->bulk_insert ) {
  511. if(empty($this->deletes[$key])) $this->deletes[$key] = array();
  512. $this->row['fv$gsn'] = $this->gsn_hwm;
  513. $this->deletes[$key][] = $this->row;
  514. if(count($this->deletes[$key]) >= 10000) {
  515. $this->process_rows();
  516. }
  517. } else {
  518. $row=array();
  519. foreach($this->row as $col) {
  520. if($col[0] == "'") {
  521. $col = trim($col,"'");
  522. }
  523. $col = mysql_real_escape_string($col);
  524. $row[] = "'$col'";
  525. }
  526. $valList = "(-1, @fv_uow_id, {$this->binlogServerId},{$this->gsn_hwm}," . implode(",", $row) . ")";
  527. $sql = sprintf("INSERT INTO `%s`.`%s` VALUES %s", $this->mvlogDB, $this->mvlog_table, $valList );
  528. my_mysql_query($sql, $this->dest) or die1("COULD NOT EXEC SQL:\n$sql\n" . mysql_error() . "\n");
  529. }
  530. }
  531. /* Called when a row is inserted, or for the new image of an UPDATE */
  532. function insert_row() {
  533. $this->gsn_hwm+=1;
  534. $key = '`' . $this->mvlogDB . '`.`' . $this->mvlog_table . '`';
  535. $this->tables[$key]=array('schema'=>$this->db ,'table'=>$this->base_table);
  536. if ( $this->bulk_insert ) {
  537. if(empty($this->inserts[$key])) $this->inserts[$key] = array();
  538. $this->row['fv$gsn'] = $this->gsn_hwm;
  539. $this->inserts[$key][] = $this->row;
  540. if(count($this->inserts[$key]) >= 10000) {
  541. $this->process_rows();
  542. }
  543. } else {
  544. $row=array();
  545. foreach($this->row as $col) {
  546. if($col[0] == "'") {
  547. $col = trim($col,"'");
  548. }
  549. $col = mysql_real_escape_string($col);
  550. $row[] = "'$col'";
  551. }
  552. $valList = "(1, @fv_uow_id, $this->binlogServerId,{$this->gsn_hwm}," . implode(",", $row) . ")";
  553. $sql = sprintf("INSERT INTO `%s`.`%s` VALUES %s", $this->mvlogDB, $this->mvlog_table, $valList );
  554. my_mysql_query($sql, $this->dest) or die1("COULD NOT EXEC SQL:\n$sql\n" . mysql_error() . "\n");
  555. }
  556. }
  557. function process_rows() {
  558. $i = 0;
  559. while($i<2) {
  560. $valList = "";
  561. if ($i==0) {
  562. $data = $this->inserts;
  563. $mode = 1;
  564. } else {
  565. $data = $this->deletes;
  566. $mode = -1;
  567. }
  568. $tables = array_keys($data);
  569. foreach($tables as $table) {
  570. $rows = $data[$table];
  571. $sql = sprintf("INSERT INTO %s VALUES ", $table);
  572. foreach($rows as $the_row) {
  573. $row = array();
  574. $gsn = $the_row['fv$gsn'];
  575. unset($the_row['fv$gsn']);
  576. foreach($the_row as $pos => $col) {
  577. if($col[0] == "'") {
  578. $col = "'" . mysql_real_escape_string(trim($col,"'")) . "'";
  579. }
  580. $datatype = $this->table_ordinal_datatype($this->tables[$table]['schema'],$this->tables[$table]['table'],$pos+1);
  581. switch(trim($datatype)) {
  582. case 'int':
  583. case 'tinyint':
  584. case 'mediumint':
  585. case 'smallint':
  586. case 'bigint':
  587. case 'serial':
  588. case 'decimal':
  589. case 'float':
  590. case 'double':
  591. if($this->table_ordinal_is_unsigned($this->tables[$table]['schema'],$this->tables[$table]['table'],$pos+1)) {
  592. if($col[0] == "-" && strpos($col, '(')) {
  593. $col = substr($col, strpos($col,'(')+1, -1);
  594. }
  595. } else {
  596. if(strpos($col,' ')) $col = substr($col,0,strpos($col,' '));
  597. }
  598. $last_point = strrpos($col, '.');
  599. $first_point = strpos($col, '.');
  600. if($last_point !== $first_point) {
  601. $mod_str=substr($col, 0, $last_point-1);
  602. $mod_str=str_replace('.','',$mod_str);
  603. $col = $mod_str .= substr($col, $last_point);
  604. }
  605. break;
  606. case 'timestamp':
  607. $col = 'from_unixtime(' . $col . ')';
  608. break;
  609. case 'datetime':
  610. $col = "'" . mysql_real_escape_string(trim($col,"'")) . "'";
  611. break;
  612. }
  613. $row[] = $col;
  614. }
  615. if($valList) $valList .= ",\n";
  616. $valList .= "($mode, @fv_uow_id, $this->binlogServerId,$gsn," . implode(",", $row) . ")";
  617. $bytes = strlen($valList) + strlen($sql);
  618. $allowed = floor($this->max_allowed_packet * .9); #allowed len is 90% of max_allowed_packet
  619. if($bytes > $allowed) {
  620. my_mysql_query($sql . $valList, $this->dest) or die1("COULD NOT EXEC SQL:\n$sql\n" . mysql_error() . "\n");
  621. $valList = "";
  622. }
  623. }
  624. if($valList) {
  625. my_mysql_query($sql . $valList, $this->dest) or die1("COULD NOT EXEC SQL:\n$sql\n" . mysql_error() . "\n");
  626. $valList = '';
  627. }
  628. }
  629. ++$i;
  630. }
  631. unset($this->inserts);
  632. unset($this->deletes);
  633. $this->inserts = array();
  634. $this->deletes = array();
  635. }
  636. /* Called for statements in the binlog. It is possible that this can be called more than
  637. * one time per event. If there is a SET INSERT_ID, SET TIMESTAMP, etc
  638. */
  639. function statement($sql) {
  640. $sql = trim($sql);
  641. #TODO: Not sure if this might be important..
  642. # In general, I think we need to worry about character
  643. # set way more than we do (which is not at all)
  644. if(substr($sql,0,6) == '/*!\C ') {
  645. return;
  646. }
  647. if($sql[0] == '/') {
  648. $end_comment = strpos($sql, ' ');
  649. $sql = trim(substr($sql, $end_comment, strlen($sql) - $end_comment));
  650. }
  651. preg_match("/([^ ]+)(.*)/", $sql, $matches);
  652. //print_r($matches);
  653. $command = $matches[1];
  654. $command = str_replace($this->delimiter,'', $command);
  655. $args = $matches[2];
  656. switch(strtoupper($command)) {
  657. #register change in delimiter so that we properly capture statements
  658. case 'DELIMITER':
  659. $this->delimiter = trim($args);
  660. break;
  661. #ignore SET and USE for now. I don't think we need it for anything.
  662. case 'SET':
  663. break;
  664. case 'USE':
  665. $this->activeDB = trim($args);
  666. $this->activeDB = str_replace($this->delimiter,'', $this->activeDB);
  667. break;
  668. #NEW TRANSACTION
  669. case 'BEGIN':
  670. $this->start_transaction();
  671. break;
  672. #END OF BINLOG, or binlog terminated early, or mysqlbinlog had an error
  673. case 'ROLLBACK':
  674. $this->rollback_transaction();
  675. break;
  676. case 'COMMIT':
  677. $this->commit_transaction();
  678. break;
  679. #Might be interestested in CREATE statements at some point, but not right now.
  680. case 'CREATE':
  681. break;
  682. #DML IS BAD....... :(
  683. case 'INSERT':
  684. case 'UPDATE':
  685. case 'DELETE':
  686. case 'REPLACE':
  687. case 'TRUNCATE':
  688. /* TODO: If the table is not being logged, ignore DML on it... */
  689. if($this->raiseWarnings) trigger_error('Detected statement DML on a table! Changes can not be tracked!' , E_USER_WARNING);
  690. break;
  691. case 'RENAME':
  692. #TODO: Find some way to make atomic rename atomic. split it up for now
  693. $tokens = FlexCDC::split_sql($sql);
  694. $clauses=array();
  695. $new_sql = '';
  696. $clause = "";
  697. for($i=4;$i<count($tokens);++$i) {
  698. #grab each alteration clause (like add column, add key or drop column)
  699. if($tokens[$i] == ',') {
  700. $clauses[] = $clause;
  701. $clause = "";
  702. } else {
  703. $clause .= $tokens[$i];
  704. }
  705. }
  706. if($clause) $clauses[] = $clause;
  707. $new_clauses = "";
  708. foreach($clauses as $clause) {
  709. $clause = trim(str_replace($this->delimiter, '', $clause));
  710. $tokens = FlexCDC::split_sql($clause);
  711. $old_table = $tokens[0];
  712. if(strpos($old_table, '.') === false) {
  713. $old_base_table = $old_table;
  714. $old_table = $this->activeDB . '.' . $old_table;
  715. $old_schema = $this->activeDB;
  716. } else {
  717. $s = explode(".", $old_table);
  718. $old_schema = $s[0];
  719. $old_base_table = $s[1];
  720. }
  721. $old_log_table = str_replace('.','_',$old_table);
  722. $new_table = $tokens[4];
  723. if(strpos($new_table, '.') === false) {
  724. $new_schema = $this->activeDB;
  725. $new_base_table = $new_table;
  726. $new_table = $this->activeDB . '.' . $new_table;
  727. } else {
  728. $s = explode(".", $new_table);
  729. $new_schema = $s[0];
  730. $new_base_table = $s[1];
  731. }
  732. $new_log_table = str_replace('.', '_', $new_table);
  733. $clause = "$old_log_table TO $new_log_table";
  734. $sql = "DELETE from `" . $this->mvlogs . "` where table_name='$old_base_table' and table_schema='$old_schema'";
  735. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error($this->dest) . "\n");
  736. $sql = "REPLACE INTO `" . $this->mvlogs . "` (mvlog_name, table_name, table_schema) values ('$new_log_table', '$new_base_table', '$new_schema')";
  737. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error($this->dest) . "\n");
  738. $sql = 'RENAME TABLE ' . $clause;
  739. @my_mysql_query($sql, $this->dest);# or die1('DURING RENAME:\n' . $new_sql . "\n" . mysql_error($this->dest) . "\n");
  740. my_mysql_query('commit', $this->dest);
  741. $this->mvlogList = array();
  742. $this->refresh_mvlog_cache();
  743. }
  744. break;
  745. #ALTER we can deal with via some clever regex, when I get to it. Need a test case
  746. #with some complex alters
  747. case 'ALTER':
  748. /* TODO: If the table is not being logged, ignore ALTER on it... If it is being logged, modify ALTER appropriately and apply to the log.*/
  749. $tokens = FlexCDC::split_sql($sql);
  750. $is_alter_table = -1;
  751. foreach($tokens as $key => $token) {
  752. if(strtoupper($token) == 'TABLE') {
  753. $is_alter_table = $key;
  754. break;
  755. }
  756. }
  757. if(!preg_match('/\s+table\s+([^ ]+)/i', $sql, $matches)) return;
  758. if(empty($this->mvlogList[str_replace('.','',trim($matches[1]))])) {
  759. return;
  760. }
  761. $table = $matches[1];
  762. #switch table name to the log table
  763. if(strpos($table, '.')) {
  764. $s = explode('.', $table);
  765. $old_schema = $s[0];
  766. $old_base_table = $s[1];
  767. } else {
  768. $old_schema = $this->activeDB;
  769. $old_base_table = $table;
  770. }
  771. unset($table);
  772. $old_log_table = $s[0] . '_' . $s[1];
  773. #IGNORE ALTER TYPES OTHER THAN TABLE
  774. if($is_alter_table>-1) {
  775. $clauses = array();
  776. $clause = "";
  777. for($i=$is_alter_table+4;$i<count($tokens);++$i) {
  778. #grab each alteration clause (like add column, add key or drop column)
  779. if($tokens[$i] == ',') {
  780. $clauses[] = $clause;
  781. $clause = "";
  782. } else {
  783. $clause .= $tokens[$i];
  784. }
  785. }
  786. $clauses[] = $clause;
  787. $new_clauses = "";
  788. $new_log_table="";
  789. $new_schema="";
  790. $new_base_Table="";
  791. foreach($clauses as $clause) {
  792. $clause = trim(str_replace($this->delimiter, '', $clause));
  793. #skip clauses we do not want to apply to mvlogs
  794. if(!preg_match('/^ORDER|^DISABLE|^ENABLE|^ADD CONSTRAINT|^ADD FOREIGN|^ADD FULLTEXT|^ADD SPATIAL|^DROP FOREIGN|^ADD KEY|^ADD INDEX|^DROP KEY|^DROP INDEX|^ADD PRIMARY|^DROP PRIMARY|^ADD PARTITION|^DROP PARTITION|^COALESCE|^REORGANIZE|^ANALYZE|^CHECK|^OPTIMIZE|^REBUILD|^REPAIR|^PARTITION|^REMOVE/i', $clause)) {
  795. #we have four "header" columns in the mvlog. Make it so that columns added as
  796. #the FIRST column on the table go after our header columns.
  797. $tokens = preg_split('/\s/', $clause);
  798. if(strtoupper($tokens[0]) == 'RENAME') {
  799. if(strtoupper(trim($tokens[1])) == 'TO') {
  800. $tokens[1] = $tokens[2];
  801. }
  802. if(strpos($tokens[1], '.') !== false) {
  803. $new_log_table = $tokens[1];
  804. $s = explode(".", $tokens[1]);
  805. $new_schema = $s[0];
  806. $new_base_table = $s[1];
  807. } else {
  808. $new_base_table = $tokens[1];
  809. $new_log_table = $this->activeDB . '.' . $tokens[1];
  810. }
  811. $new_log_table = str_replace('.', '_', $new_log_table);
  812. $clause = "RENAME TO $new_log_table";
  813. }
  814. if(strtoupper($tokens[0]) == 'ADD' && strtoupper($tokens[count($tokens)-1]) == 'FIRST') {
  815. $tokens[count($tokens)-1] = 'AFTER `fv$gsn`';
  816. $clause = join(' ', $tokens);
  817. }
  818. if($new_clauses) $new_clauses .= ', ';
  819. $new_clauses .= $clause;
  820. }
  821. }
  822. if($new_clauses) {
  823. $new_alter = 'ALTER TABLE ' . $old_log_table . ' ' . $new_clauses;
  824. my_mysql_query($new_alter, $this->dest) or die1($new_alter. "\n" . mysql_error($this->dest) . "\n");
  825. if($new_log_table) {
  826. $sql = "DELETE from `" . $this->mvlogs . "` where table_name='$old_base_table' and table_schema='$old_schema'";
  827. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error($this->dest) . "\n");
  828. $sql = "INSERT INTO `" . $this->mvlogs . "` (mvlog_name, table_name, table_schema) values ('$new_log_table', '$new_base_table', '$new_schema')";
  829. my_mysql_query($sql, $this->dest) or die1($sql . "\n" . mysql_error($this->dest) . "\n");
  830. $this->mvlogList = array();
  831. $this->refresh_mvlog_cache();
  832. }
  833. }
  834. }
  835. break;
  836. #DROP probably isn't bad. We might be left with an orphaned change log.
  837. case 'DROP':
  838. /* TODO: If the table is not being logged, ignore DROP on it.
  839. * If it is being logged then drop the log and maybe any materialized views that use the table..
  840. * Maybe throw an errro if there are materialized views that use a table which is dropped... (TBD)*/
  841. if($this->raiseWarnings) trigger_error('Detected DROP on a table! This may break CDC, particularly if the table is recreated with a different structure.' , E_USER_WARNING);
  842. break;
  843. #I might have missed something important. Catch it.
  844. #Maybe this should be E_USER_ERROR
  845. default:
  846. #if($this->raiseWarnings) trigger_error('Unknown command: ' . $command, E_USER_WARNING);
  847. if($this->raiseWarnings) trigger_error('Unknown command: ' . $command, E_USER_WARNING);
  848. break;
  849. }
  850. }
  851. static function ignore_clause($clause) {
  852. $clause = trim($clause);
  853. if(preg_match('/^(?:ADD|DROP)\s+(?:PRIMARY KEY|KEY|INDEX)')) {
  854. return true;
  855. }
  856. return false;
  857. }
  858. function process_binlog($proc, $lastLine="") {
  859. $binlogStatement="";
  860. $this->timeStamp = false;
  861. $this->refresh_mvlog_cache();
  862. $sql = "";
  863. #read from the mysqlbinlog process one line at a time.
  864. #note the $lastLine variable - we process rowchange events
  865. #in another procedure which also reads from $proc, and we
  866. #can't seek backwards, so this function returns the next line to process
  867. #In this case we use that line instead of reading from the file again
  868. while( !feof($proc) ) {
  869. if($lastLine) {
  870. #use a previously saved line (from process_rowlog)
  871. $line = $lastLine;
  872. $lastLine = "";
  873. } else {
  874. #read from the process
  875. $line = trim(fgets($proc));
  876. }
  877. #echo "-- $line\n";
  878. #It is faster to check substr of the line than to run regex
  879. #on each line.
  880. $prefix=substr($line, 0, 5);
  881. if($prefix=="ERROR") {
  882. if(preg_match('/Got error/', $line))
  883. die1("error from mysqlbinlog: $line");
  884. }
  885. $matches = array();
  886. #Control information from MySQLbinlog is prefixed with a hash comment.
  887. if($prefix[0] == "#") {
  888. $binlogStatement = "";
  889. if (preg_match('/^#([0-9]+\s+[0-9:]+)\s+server\s+id\s+([0-9]+)\s+end_log_pos ([0-9]+).*/', $line,$matches)) {
  890. $this->timeStamp = $matches[1];
  891. $this->binlogPosition = $matches[3];
  892. $this->binlogServerId = $matches[2];
  893. #$this->set_capture_pos();
  894. } else {
  895. #decoded RBR changes are prefixed with ###
  896. if($prefix == "### I" || $prefix == "### U" || $prefix == "### D") {
  897. if(preg_match('/### (UPDATE|INSERT INTO|DELETE FROM)\s([^.]+)\.(.*$)/', $line, $matches)) {
  898. $this->db = $matches[2];
  899. $this->base_table = $matches[3];
  900. if($this->db == $this->mvlogDB && $this->base_table == $this->mvlogs) {
  901. $this->refresh_mvlog_cache();
  902. }
  903. if(empty($this->mvlogList[$this->db . $this->base_table])) {
  904. if($this->auto_changelog && !strstr($this->base_table,'_delta') ) {
  905. $this->create_mvlog($this->db, $this->base_table);
  906. $this->refresh_mvlog_cache();
  907. }
  908. }
  909. if(!empty($this->mvlogList[$this->db . $this->base_table])) {
  910. $this->mvlog_table = $this->mvlogList[$this->db . $this->base_table];
  911. $lastLine = $this->process_rowlog($proc, $line);
  912. }
  913. }
  914. }
  915. }
  916. } else {
  917. if($binlogStatement) {
  918. $binlogStatement .= " ";
  919. }
  920. $binlogStatement .= $line;
  921. $pos=false;
  922. if(($pos = strpos($binlogStatement, $this->delimiter)) !== false) {
  923. #process statement
  924. $this->statement($binlogStatement);
  925. $binlogStatement = "";
  926. }
  927. }
  928. }
  929. }
  930. function process_rowlog($proc) {
  931. $sql = "";
  932. $skip_rows = false;
  933. $line = "";
  934. #if there is a list of databases, and this database is not on the list
  935. #then skip the rows
  936. if(!empty($this->onlyDatabases) && empty($this->onlyDatabases[trim($this->db)])) {
  937. $skip_rows = true;
  938. }
  939. # loop over the input, collecting all the input values into a set of INSERT statements
  940. $this->row = array();
  941. $mode = 0;
  942. while($line = fgets($proc)) {
  943. $line = trim($line);
  944. #DELETE and UPDATE statements contain a WHERE clause with the OLD row image
  945. if($line == "### WHERE") {
  946. if(!empty($this->row)) {
  947. switch($mode) {
  948. case -1:
  949. $this->delete_row();
  950. break;
  951. case 1:
  952. $this->insert_row();
  953. break;
  954. default:
  955. die1('UNEXPECTED MODE IN PROCESS_ROWLOG!');
  956. }
  957. $this->row = array();
  958. }
  959. $mode = -1;
  960. #INSERT and UPDATE statements contain a SET clause with the NEW row image
  961. } elseif($line == "### SET") {
  962. if(!empty($this->row)) {
  963. switch($mode) {
  964. case -1:
  965. $this->delete_row();
  966. break;
  967. case 1:
  968. $this->insert_row();
  969. break;
  970. default:
  971. die1('UNEXPECTED MODE IN PROCESS_ROWLOG!');
  972. }
  973. $this->row = array();
  974. }
  975. $mode = 1;
  976. /*} elseif(preg_match('/###\s+@[0-9]+=(-[0-9]*) .*$/', $line, $matches)) {
  977. $this->row[] = $matches[1];
  978. */
  979. #Row images are in format @1 = 'abc'
  980. # @2 = 'def'
  981. #Where @1, @2 are the column number in the table
  982. } elseif(preg_match('/###\s+@[0-9]+=(.*)$/', $line, $matches)) {
  983. $this->row[] = $matches[1];
  984. #This line does not start with ### so we are at the end of the images
  985. } else {
  986. #echo ":: $line\n";
  987. if(!$skip_rows) {
  988. switch($mode) {
  989. case -1:
  990. $this->delete_row();
  991. break;
  992. case 1:
  993. $this->insert_row();
  994. break;
  995. default:
  996. die1('UNEXPECTED MODE IN PROCESS_ROWLOG!');
  997. }
  998. }
  999. $this->row = array();
  1000. break; #out of while
  1001. }
  1002. #keep reading lines
  1003. }
  1004. #return the last line so that we can process it in the parent body
  1005. #you can't seek backwards in a proc stream...
  1006. return $line;
  1007. }
  1008. function drop_mvlog($schema, $table) {
  1009. #will implicit commit
  1010. $sql = "DROP TABLE IF EXISTS " . $this->mvlogDB . "." . "`%s_%s`";
  1011. $sql = sprintf($sql, mysql_real_escape_string($schema), mysql_real_escape_string($table));
  1012. if(!my_mysql_query($sql)) return false;
  1013. my_mysql_query("BEGIN", $this->dest);
  1014. $sql = "DELETE FROM " . $this->mvlogDB . ". " . $this->mvlogs . " where table_schema = '%s' and table_name = '%s'";
  1015. $sql = sprintf($sql, mysql_real_escape_string($schema), mysql_real_escape_string($table));
  1016. if(!my_mysql_query($sql)) return false;
  1017. return my_mysql_query('commit');
  1018. }
  1019. #AUTOPORTED FROM FLEXVIEWS.CREATE_MVLOG() w/ minor modifications for PHP
  1020. function create_mvlog($v_schema_name,$v_table_name) {
  1021. $v_done=FALSE;
  1022. $v_column_name=NULL;
  1023. $v_data_type=NULL;
  1024. $v_sql=NULL;
  1025. $cursor_sql = "SELECT COLUMN_NAME, IF(COLUMN_TYPE='TIMESTAMP', 'TIMESTAMP', COLUMN_TYPE) COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='$v_table_name' AND TABLE_SCHEMA = '$v_schema_name'";
  1026. $cur_columns = my_mysql_query($cursor_sql, $this->source);
  1027. $v_sql = '';
  1028. while(1) {
  1029. if( $v_sql != '' ) {
  1030. $v_sql = FlexCDC::concat($v_sql, ', ');
  1031. }
  1032. $row = mysql_fetch_array($cur_columns);
  1033. if( $row === false ) $v_done = true;
  1034. if( $row ) {
  1035. $v_column_name = '`'. $row[0] . '`';
  1036. $v_data_type = $row[1];
  1037. }
  1038. if( $v_done ) {
  1039. mysql_free_result($cur_columns);
  1040. break;
  1041. }
  1042. $v_sql = FlexCDC::concat($v_sql, $v_column_name, ' ', $v_data_type);
  1043. }
  1044. if( trim( $v_sql ) == "" ) {
  1045. trigger_error('Could not access table:' . $v_table_name, E_USER_ERROR);
  1046. }
  1047. $v_sql = FlexCDC::concat('CREATE TABLE IF NOT EXISTS`', $this->mvlogDB ,'`.`' ,$v_schema_name, '_', $v_table_name,'` ( dml_type INT DEFAULT 0, uow_id BIGINT, `fv$server_id` INT UNSIGNED,fv$gsn bigint, ', $v_sql, 'KEY(uow_id, dml_type) ) ENGINE=INNODB');
  1048. $create_stmt = my_mysql_query($v_sql, $this->dest);
  1049. if(!$create_stmt) die1('COULD NOT CREATE MVLOG. ' . $v_sql . "\n");
  1050. $exec_sql = " INSERT IGNORE INTO `". $this->mvlogDB . "`.`" . $this->mvlogs . "`( table_schema , table_name , mvlog_name ) values('$v_schema_name', '$v_table_name', '" . $v_schema_name . "_" . $v_table_name . "')";
  1051. my_mysql_query($exec_sql) or die1($exec_sql . ':' . mysql_error($this->dest) . "\n");
  1052. return true;
  1053. }
  1054. }