Guest User

Untitled

a guest
Oct 25th, 2017
423
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.84 KB | None | 0 0
  1. <?php
  2. /*
  3. * pgBackupRestore v2
  4. * Date: 30th November 2007
  5. * Author: Michele Brodoloni <michele.brodoloni@xtnet.it>
  6. *
  7. * Changelog:
  8. * - Fixed issue with bytea fields
  9. * - Fixed issue with empty values in NOT NULL fields
  10. * - Added custom header
  11. * - Added 2 more options to backup data preserving database structure (DataOnly, UseTruncateTable)
  12. * - Added some default statements included in every backup file (~ line 227)
  13. * - Added encoding support
  14. * - Improved error checking
  15. */
  16.  
  17. class pgBackupRestore
  18. {
  19. //------------------------------------//
  20. //---[ Configuration variables ]---//
  21. //---| SET THEM FROM YOUR SCRIPT |---//
  22. //------------------------------------//
  23.  
  24. // Header to be written on file
  25. var $Header = "";
  26.  
  27. // Remove comments from SQL file ( pgBackupRestore::commentSQL() method )
  28. var $StripComments = false;
  29.  
  30. // Include table names into INSERT statement
  31. var $UseCompleteInsert = false;
  32.  
  33. // Drop the table before re-creating it
  34. var $UseDropTable = true;
  35.  
  36. // Adds TRUNCATE TABLE statement (for data only dump)
  37. var $UseTruncateTable = false;
  38.  
  39. // Dump table structure only, not data
  40. var $StructureOnly = false;
  41.  
  42. // Dump only table data without structure
  43. var $DataOnly = false;
  44.  
  45. // Script keeps running after encountering a fatal error
  46. var $IgnoreFatalErrors = false;
  47.  
  48. // Database Encoding
  49. // (Supported are: SQL_ASCII and UTF8. Unknown behaviour with others.)
  50. var $Encoding = "SQL_ASCII";
  51.  
  52. //------------------------------------//
  53. //---| NO NEED TO EDIT BELOW HERE |---//
  54. //------------------------------------//
  55.  
  56. //---[ File related variables
  57. var $fpSQL;
  58.  
  59. //---[ Database related variables
  60. var $Connected = false;
  61. var $Database;
  62. var $Link_ID;
  63. var $Query_ID;
  64. var $Record = array();
  65. var $Tables = array();
  66. var $BackupOnlyTables = array();
  67. var $ExcludeTables = array();
  68. var $Row = 0;
  69.  
  70. //---[ Error Handling
  71. var $GotSQLerror = false;
  72. var $LastSQLerror = "";
  73.  
  74. //---[ Protected keywords
  75. var $pKeywords = array("desc");
  76.  
  77. # CLASS CONSTRUCTOR
  78. function pgBackupRestore($uiHost, $uiUser, $uiPassword, $uiDatabase, $uiPort = 5432)
  79. {
  80. $this->Link_ID = pg_pconnect("host=${uiHost} port=${uiPort} dbname=${uiDatabase} user=${uiUser} password=${uiPassword}");
  81. if (!$this->Link_ID)
  82. $this->Error("Can't connect to the Postgres Database", true);
  83. $this->Database = $uiDatabase;
  84. $this->Connected = ($this->Link_ID) ? true : false;
  85. pg_set_client_encoding($this->Link_ID, $this->Encoding);
  86. }
  87.  
  88. function _FixOptions()
  89. {
  90. // Checks and fix for incompatible options
  91. if ($this->StructureOnly)
  92. {
  93. $this->DataOnly = false;
  94. $this->UseTruncateTable = false;
  95. }
  96.  
  97. if ($this->DataOnly)
  98. {
  99. $this->StructureOnly = false;
  100. $this->UseDropTable = false;
  101. }
  102. }
  103.  
  104. #------------------------#
  105. # SQL RELATIVE FUNCTIONS #
  106. #------------------------#
  107.  
  108. // Queries the PostgreSQL database.
  109. // If a SQL error is encountered it will be written on
  110. // $this->LastSQLerror variable and $this->GotSQLerror
  111. // will be set to TRUE. Returns the query id.
  112. //
  113. function query($uiSQL)
  114. {
  115. if (!$this->Connected) return (false);
  116. $this->Row = 0;
  117. $this->Query_ID = @pg_query($this->Link_ID, $uiSQL);
  118. $this->LastSQLerror = trim(str_replace("ERROR:", "", pg_last_error($this->Link_ID)));
  119. $this->GotSQLerror = ($this->LastSQLerror) ? true : false;
  120. return $this->Query_ID;
  121. }
  122.  
  123. // Returns the next record of a query resultset.
  124. // Values can be accessed through $this->Record[field_name]
  125. // or by $this->Record[field_id] (see pg_fetch_array())
  126. //
  127. function next_record()
  128. {
  129. if (!$this->Query_ID) return (false);
  130.  
  131. $this->Record = @pg_fetch_array($this->Query_ID, $this->Row++);
  132. if (is_array($this->Record))
  133. return(true);
  134. else
  135. {
  136. pg_free_result($this->Query_ID);
  137. $this->Query_ID = 0;
  138. return(false);
  139. }
  140. }
  141.  
  142. // Returns a value from a record.
  143. // Just pass the wanted field name to this.
  144. //
  145. function get($uiField)
  146. {
  147. if (is_array($this->Record) && array_key_exists($uiField, $this->Record))
  148. return $this->Record[$uiField];
  149. else
  150. return (NULL);
  151. }
  152.  
  153. // Returns an array containing the field names
  154. // returned by a query.
  155. // Useful when doing a "SELECT * FROM table" query
  156. //
  157. function field_names()
  158. {
  159. if (!$this->Query_ID) return(false);
  160. $n = @pg_num_fields($this->Query_ID);
  161. $columns = Array();
  162.  
  163. for ($i=0; $i<$n ; $i++ )
  164. $columns[] = @pg_field_name($this->Query_ID, $i);
  165.  
  166. return $columns;
  167. }
  168.  
  169. // Return a quoted string if the $this->pKeywords array
  170. // contains it. It is used when a table name match
  171. // a PostgreSQL keyword such as "DESC", "PRIMARY"
  172. // and others, causing a SQL syntax error when restoring
  173. //
  174. function escape_keyword($uiKeyword)
  175. {
  176. if (in_array($uiKeyword, $this->pKeywords))
  177. return('"'.$uiKeyword.'"');
  178. else
  179. return($uiKeyword);
  180. }
  181.  
  182. #--------------------------#
  183. # CLASS RELATIVE FUNCTIONS #
  184. #--------------------------#
  185.  
  186. // Writes text into the SQL file
  187. // Called within $this->Backup() method.
  188. //
  189. function writeSQL($uiString)
  190. {
  191. if (!$this->fpSQL) return(false);
  192. fwrite($this->fpSQL, $uiString);
  193. }
  194.  
  195. // Writes comments into the SQL file when
  196. // $this->StripComments is set to FALSE
  197. // Called within $this->Backup() method.
  198. //
  199. function commentSQL($uiComment)
  200. {
  201. if (!$this->fpSQL) return(false);
  202.  
  203. if (!$StripComments)
  204. $this->writeSQL("-- $uiComment");
  205. }
  206.  
  207. // Creates a SQL file containing structure, data, indexes
  208. // relationships, sequences and so on..
  209. //
  210. function Backup($uiFilename = NULL)
  211. {
  212. if (!$this->Connected) return (false);
  213.  
  214. if (is_null($uiFilename))
  215. $this->Filename = $this->Database.".sql";
  216. else
  217. $this->Filename = $uiFilename;
  218.  
  219. // Fix incompatible flags
  220. $this->_FixOptions();
  221.  
  222. //---[ PASS 1: Opening SQL File for writing
  223.  
  224. $this->fpSQL = @fopen($this->Filename, "w");
  225. if (!$this->fpSQL)
  226. $this->Error("Can't open ". $this->Filename ." for writing!", true);
  227.  
  228. // Writes header to file if string Header is not empty
  229. if(!empty($this->Header)) $this->writeSQL($this->Header."\n");
  230.  
  231. //---[ PASS 1.1: Set default options
  232. $this->commentSQL("Default options\n");
  233. $this->writeSQL("SET client_encoding = '{$this->Encoding}';\n");
  234. $this->writeSQL("SET standard_conforming_strings = off;\n");
  235. $this->writeSQL("SET check_function_bodies = false;\n");
  236. $this->writeSQL("SET client_min_messages = warning;\n");
  237. $this->writeSQL("SET escape_string_warning = off;\n");
  238. $this->writeSQL("\n");
  239.  
  240. //---[ PASS 2: Obtaining table list from database
  241. // If the tables array is not empy, it means that
  242. // the method $this->BackupOnlyTables was used
  243. if (empty($this->Tables))
  244. {
  245. $SQL = "SELECT relname AS tablename\n".
  246. "FROM pg_class WHERE relkind IN ('r')\n".
  247. "AND relname NOT LIKE 'pg_%' AND relname NOT LIKE 'sql_%' ORDER BY tablename\n";
  248. $this->query($SQL);
  249.  
  250. // Checks if the current table is in the exclude array.
  251. while ($this->next_record())
  252. {
  253. $Table = $this->get("tablename");
  254. if (!in_array($Table, $this->ExcludeTables))
  255. $this->Tables[] = $this->escape_keyword($Table);
  256. }
  257. }
  258.  
  259. //---[ PASS 3: Generating structure for each table
  260. foreach($this->Tables as $Table)
  261. {
  262. // Use DROP TABLE statement before INSERT ?
  263. if ($this->UseDropTable)
  264. $this->writeSQL("DROP TABLE ${Table} CASCADE;\n");
  265. elseif ($this->UseTruncateTable)
  266. $this->writeSQL("TRUNCATE TABLE ${Table};\n");
  267.  
  268. if (!$this->DataOnly)
  269. {
  270. $_sequences = array();
  271.  
  272. $this->commentSQL("Structure for table '${Table}'\n");
  273.  
  274. $strSQL .= "CREATE TABLE ${Table} (";
  275.  
  276. $SQL = "SELECT attnum, attname, typname, atttypmod-4 AS atttypmod, attnotnull, atthasdef, adsrc AS def\n".
  277. "FROM pg_attribute, pg_class, pg_type, pg_attrdef\n".
  278. "WHERE pg_class.oid=attrelid\n".
  279. "AND pg_type.oid=atttypid AND attnum>0 AND pg_class.oid=adrelid AND adnum=attnum\n".
  280. "AND atthasdef='t' AND lower(relname)='${Table}' UNION\n".
  281. "SELECT attnum, attname, typname, atttypmod-4 AS atttypmod, attnotnull, atthasdef, '' AS def\n".
  282. "FROM pg_attribute, pg_class, pg_type WHERE pg_class.oid=attrelid\n".
  283. "AND pg_type.oid=atttypid AND attnum>0 AND atthasdef='f' AND lower(relname)='${Table}'\n";
  284. $this->query($SQL);
  285. while ( $this->next_record() )
  286. {
  287. $_attnum = $this->get('attnum');
  288. $_attname = $this->escape_keyword( $this->get('attname') );
  289. $_typname = $this->get('typname');
  290. $_atttypmod = $this->get('atttypmod');
  291. $_attnotnull = $this->get('attnotnull');
  292. $_atthasdef = $this->get('atthasdef');
  293. $_def = $this->get('def');
  294.  
  295. if (preg_match("/^nextval/", $_def))
  296. {
  297. $_t = explode("'", $_def);
  298. $_sequences[] = $_t[1];
  299. }
  300.  
  301. $strSQL .= "${_attname} ${_typname}";
  302. if ($_typname == "varchar") $strSQL .= "(${_atttypmod})";
  303. if ($_attnotnull == "t") $strSQL .= " NOT NULL";
  304. if ($_atthasdef == "t") $strSQL .= " DEFAULT ${_def}";
  305. $strSQL .= ",";
  306. }
  307. $strSQL = rtrim($strSQL, ",");
  308. $strSQL .= ");\n";
  309.  
  310. //--[ PASS 3.1: Creating sequences
  311. if ($_sequences)
  312. {
  313. foreach($_sequences as $_seq_name)
  314. {
  315. $SQL = "SELECT * FROM ${_seq_name}\n";
  316. $this->query($SQL);
  317. $this->next_record();
  318.  
  319. $_incrementby = $this->get('increment_by');
  320. $_minvalue = $this->get('min_value');
  321. $_maxvalue = $this->get('max_value');
  322. $_lastvalue = $this->get('last_value');
  323. $_cachevalue = $this->get('cache_value');
  324.  
  325. $this->writeSQL("CREATE SEQUENCE ${_seq_name} INCREMENT ${_incrementby} MINVALUE ${_minvalue} ".
  326. "MAXVALUE ${_maxvalue} START ${_lastvalue} CACHE ${_cachevalue};\n");
  327. }
  328. }
  329. $this->writeSQL($strSQL);
  330. }
  331.  
  332. if (!$this->StructureOnly || $this->DataOnly)
  333. {
  334. $field_attribs = array();
  335. //---[ PASS 4: Generating INSERTs for data
  336. $this->commentSQL("Data for table '${Table}'\n");
  337.  
  338. //---[ PASS 4.1: Get field attributes to check if it's null or bytea (to be escaped)
  339. $SQL = "SELECT * FROM ${Table} LIMIT 0;\n";
  340. $this->query($SQL);
  341. $fields = $this->field_names();
  342.  
  343. foreach ($fields as $Field)
  344. $field_attribs[$Field] = $this->GetFieldInfo($Table, $Field);
  345. //---| END PASS 4.1
  346.  
  347. $SQL = "SELECT * FROM ${Table}\n";
  348. $this->query($SQL);
  349.  
  350. while ( $this->next_record() )
  351. {
  352. $Record = array();
  353. foreach($fields as $f)
  354. {
  355. $data = $this->get($f);
  356. if ($field_attribs[$f]['is_binary'])
  357. { // Binary Data
  358. $Record[$f] = addcslashes(pg_escape_bytea($data),"\$");
  359. }
  360. else
  361. { // Strings
  362. $data = preg_replace("/\x0a/", "", $data);
  363. $data = preg_replace("/\x0d/", "\r", $data);
  364. $Record[$f] = pg_escape_string(trim($data));
  365. }
  366. }
  367. $FieldNames = ($this->UseCompleteInsert) ? "(".implode(",",$fields).")" : "";
  368.  
  369. $strSQL = "INSERT INTO ${Table}${FieldNames} VALUES({". (implode("},{",$fields))."});";
  370. foreach($fields as $f)
  371. {
  372. if ($Record[$f] != '')
  373. $str = sprintf("'%s'", $Record[$f]);
  374. else
  375. $str = ($field_attribs[$f]['not_null']) ? "''" : "NULL";
  376.  
  377. $strSQL = preg_replace("/{".$f."}/", $str, $strSQL);
  378. }
  379. $this->writeSQL($strSQL."\n");
  380. unset($strSQL);
  381. }
  382. }
  383.  
  384. if (!$this->DataOnly)
  385. {
  386. //---[ PASS 5: Generating data indexes (Primary)
  387. $this->commentSQL("Indexes for table '${Table}'\n");
  388.  
  389. $SQL = "SELECT pg_index.indisprimary, pg_catalog.pg_get_indexdef(pg_index.indexrelid)\n".
  390. "FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index AS pg_index\n".
  391. "WHERE c.relname = '${Table}'\n".
  392. "AND c.oid = pg_index.indrelid\n".
  393. "AND pg_index.indexrelid = c2.oid\n";
  394. $this->query($SQL);
  395. while ( $this->next_record() )
  396. {
  397. $_pggetindexdef = $this->get('pg_get_indexdef');
  398. $_indisprimary = $this->get('indisprimary');
  399.  
  400. if (eregi("^CREATE UNIQUE INDEX", $_pggetindexdef))
  401. {
  402. $_keyword = ($_indisprimary == 't') ? 'PRIMARY KEY' : 'UNIQUE';
  403. $strSQL = str_replace("CREATE UNIQUE INDEX", "" , $this->get('pg_get_indexdef'));
  404. $strSQL = str_replace("USING btree", "|", $strSQL);
  405. $strSQL = str_replace("ON", "|", $strSQL);
  406. $strSQL = str_replace("\x20","", $strSQL);
  407. list($_pkey, $_tablename, $_fieldname) = explode("|", $strSQL);
  408. $this->writeSQL("ALTER TABLE ONLY ${_tablename} ADD CONSTRAINT ${_pkey} ${_keyword} ${_fieldname};\n");
  409. unset($strSQL);
  410. }
  411. else $this->writeSQL("${_pggetindexdef};\n");
  412. }
  413.  
  414. //---[ PASS 6: Generating relationships
  415. $this->commentSQL("Relationships for table '${Table}'\n");
  416.  
  417. $SQL = "SELECT cl.relname AS table, ct.conname, pg_get_constraintdef(ct.oid)\n".
  418. "FROM pg_catalog.pg_attribute a\n".
  419. "JOIN pg_catalog.pg_class cl ON (a.attrelid = cl.oid AND cl.relkind = 'r')\n".
  420. "JOIN pg_catalog.pg_namespace n ON (n.oid = cl.relnamespace)\n".
  421. "JOIN pg_catalog.pg_constraint ct ON (a.attrelid = ct.conrelid AND ct.confrelid != 0 AND ct.conkey[1] = a.attnum)\n".
  422. "JOIN pg_catalog.pg_class clf ON (ct.confrelid = clf.oid AND clf.relkind = 'r')\n".
  423. "JOIN pg_catalog.pg_namespace nf ON (nf.oid = clf.relnamespace)\n".
  424. "JOIN pg_catalog.pg_attribute af ON (af.attrelid = ct.confrelid AND af.attnum = ct.confkey[1]) order by cl.relname\n";
  425. $this->query($SQL);
  426. while ( $this->next_record() )
  427. {
  428. $_table = $this->get('table');
  429. $_conname = $this->get('conname');
  430. $_constraintdef = $this->get('pg_get_constraintdef');
  431. $this->writeSQL("ALTER TABLE ONLY ${_table} ADD CONSTRAINT ${_conname} ${_constraintdef};\n");
  432. }
  433. }
  434. }
  435. //---[ PASS 7: Closing SQL File
  436. fclose($this->fpSQL);
  437.  
  438. return (filesize($this->Filename) > 0)? true : false;
  439. }
  440.  
  441. // Checks if a field can be null, in order to replace it with '' or NULL
  442. // when building backup SQL statements
  443. function GetFieldInfo($uiTable, $uiField)
  444. {
  445.  
  446. if (!$this->Connected) return(false);
  447. $response = array();
  448.  
  449. $SQL = "SELECT typname, attnotnull \n".
  450. "FROM pg_attribute, pg_class, pg_type WHERE pg_class.oid=attrelid \n".
  451. "AND pg_type.oid=atttypid AND attnum>0 AND lower(relname)='${uiTable}' and attname = '${uiField}';\n";
  452.  
  453. $this->query($SQL);
  454. $this->next_record();
  455.  
  456. $not_null = $this->get('attnotnull');
  457. $field_type = $this->get('typname');
  458.  
  459. $response['not_null'] = ($not_null == 't') ? true : false;
  460. $response['is_binary'] = ($field_type == 'bytea') ? true : false;
  461.  
  462. return $response;
  463. }
  464.  
  465. // Restore the database from a SQL file
  466. //
  467. function Restore($uiFilename = NULL)
  468. {
  469. $this->Errors = array();
  470. if (!$this->Connected) return(false);
  471.  
  472. if (is_null($uiFilename))
  473. $this->Filename = $this->Database.".sql";
  474. else
  475. $this->Filename = $uiFilename;
  476.  
  477.  
  478. if (!is_readable($this->Filename))
  479. $this->Error("Can't find {$this->Filename} for opening", true);
  480.  
  481. $_CurrentLine = 0;
  482. $_fpSQL = fopen($this->Filename, "r");
  483. while ( $_readSQL = fgets($_fpSQL) )
  484. {
  485. $_CurrentLine++;
  486. if (preg_match("/^-/", $_readSQL) || preg_match("/^[\s]+$/", $_readSQL)) continue; // Don't bother about comments and blank lines
  487. if ($this->Encoding == 'UTF8')
  488. $this->query(utf8_encode($_readSQL));
  489. else
  490. $this->query($_readSQL);
  491. if ($this->GotSQLerror)
  492. $this->Error("SQL syntax error on line ${_CurrentLine} (". $this->LastSQLerror .")", true);
  493. }
  494. }
  495.  
  496. // Use this method when you don't need to backup
  497. // some specific tables. The passed value can
  498. // be a string or an array.
  499. //
  500. function ExcludeTables($uiTables)
  501. {
  502. if (empty($uiTables)) return(false);
  503.  
  504. if (is_array($uiTables))
  505. foreach ($uiTables as $item)
  506. $this->ExcludeTables[] = $item;
  507. else
  508. $this->ExcludeTables[] = $uiTables;
  509. }
  510.  
  511. // Use this methon when you need to backup
  512. // ONLY some specific tables. The passed value
  513. // can be a string or an array.
  514. //
  515. function BackupOnlyTables($uiTables)
  516. {
  517. if (empty($uiTables)) return(false);
  518.  
  519. if (is_array($uiTables))
  520. foreach ($uiTables as $item)
  521. $this->Tables[] = $item;
  522. else
  523. $this->Tables[] = $uiTables;
  524. }
  525.  
  526. // Error printing function.
  527. // When outputting a fatal error it will exit the script.
  528. // php-cli coloured output included ;)
  529. //
  530. function Error($uiErrStr, $uiFatal = false)
  531. {
  532. $_error = "";
  533. $_error_type = ($uiFatal) ? "Fatal Error" : "Error";
  534.  
  535. if ($_SERVER['TERM']) // we're using php-cli
  536. printf("%c[%d;%d;%dm%s: %c[%dm%s\n", 0x1B, 1, 31, 40, $_error_type, 0x1B, 0, $uiErrStr);
  537. else
  538. printf("<font face='tahoma' size='2'><b>%s:</b>&nbsp;%s</font><br>\n", $_error_type, $uiErrStr);
  539.  
  540. if ($uiFatal && !$this->IgnoreFatalErrors) exit;
  541. }
  542.  
  543. }
  544. ?>
Add Comment
Please, Sign In to add comment