adodb-datadict.inc.php 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. <?php
  2. /**
  3. @version v5.20.17 31-Mar-2020
  4. @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  5. @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
  6. Released under both BSD license and Lesser GPL library license.
  7. Whenever there is any discrepancy between the two licenses,
  8. the BSD license will take precedence.
  9. Set tabs to 4 for best viewing.
  10. DOCUMENTATION:
  11. See adodb/tests/test-datadict.php for docs and examples.
  12. */
  13. /*
  14. Test script for parser
  15. */
  16. // security - hide paths
  17. if (!defined('ADODB_DIR')) die();
  18. function Lens_ParseTest()
  19. {
  20. $str = "`zcol ACOL` NUMBER(32,2) DEFAULT 'The \"cow\" (and Jim''s dog) jumps over the moon' PRIMARY, INTI INT AUTO DEFAULT 0, zcol2\"afs ds";
  21. print "<p>$str</p>";
  22. $a= Lens_ParseArgs($str);
  23. print "<pre>";
  24. print_r($a);
  25. print "</pre>";
  26. }
  27. if (!function_exists('ctype_alnum')) {
  28. function ctype_alnum($text) {
  29. return preg_match('/^[a-z0-9]*$/i', $text);
  30. }
  31. }
  32. //Lens_ParseTest();
  33. /**
  34. Parse arguments, treat "text" (text) and 'text' as quotation marks.
  35. To escape, use "" or '' or ))
  36. Will read in "abc def" sans quotes, as: abc def
  37. Same with 'abc def'.
  38. However if `abc def`, then will read in as `abc def`
  39. @param endstmtchar Character that indicates end of statement
  40. @param tokenchars Include the following characters in tokens apart from A-Z and 0-9
  41. @returns 2 dimensional array containing parsed tokens.
  42. */
  43. function Lens_ParseArgs($args,$endstmtchar=',',$tokenchars='_.-')
  44. {
  45. $pos = 0;
  46. $intoken = false;
  47. $stmtno = 0;
  48. $endquote = false;
  49. $tokens = array();
  50. $tokens[$stmtno] = array();
  51. $max = strlen($args);
  52. $quoted = false;
  53. $tokarr = array();
  54. while ($pos < $max) {
  55. $ch = substr($args,$pos,1);
  56. switch($ch) {
  57. case ' ':
  58. case "\t":
  59. case "\n":
  60. case "\r":
  61. if (!$quoted) {
  62. if ($intoken) {
  63. $intoken = false;
  64. $tokens[$stmtno][] = implode('',$tokarr);
  65. }
  66. break;
  67. }
  68. $tokarr[] = $ch;
  69. break;
  70. case '`':
  71. if ($intoken) $tokarr[] = $ch;
  72. case '(':
  73. case ')':
  74. case '"':
  75. case "'":
  76. if ($intoken) {
  77. if (empty($endquote)) {
  78. $tokens[$stmtno][] = implode('',$tokarr);
  79. if ($ch == '(') $endquote = ')';
  80. else $endquote = $ch;
  81. $quoted = true;
  82. $intoken = true;
  83. $tokarr = array();
  84. } else if ($endquote == $ch) {
  85. $ch2 = substr($args,$pos+1,1);
  86. if ($ch2 == $endquote) {
  87. $pos += 1;
  88. $tokarr[] = $ch2;
  89. } else {
  90. $quoted = false;
  91. $intoken = false;
  92. $tokens[$stmtno][] = implode('',$tokarr);
  93. $endquote = '';
  94. }
  95. } else
  96. $tokarr[] = $ch;
  97. }else {
  98. if ($ch == '(') $endquote = ')';
  99. else $endquote = $ch;
  100. $quoted = true;
  101. $intoken = true;
  102. $tokarr = array();
  103. if ($ch == '`') $tokarr[] = '`';
  104. }
  105. break;
  106. default:
  107. if (!$intoken) {
  108. if ($ch == $endstmtchar) {
  109. $stmtno += 1;
  110. $tokens[$stmtno] = array();
  111. break;
  112. }
  113. $intoken = true;
  114. $quoted = false;
  115. $endquote = false;
  116. $tokarr = array();
  117. }
  118. if ($quoted) $tokarr[] = $ch;
  119. else if (ctype_alnum($ch) || strpos($tokenchars,$ch) !== false) $tokarr[] = $ch;
  120. else {
  121. if ($ch == $endstmtchar) {
  122. $tokens[$stmtno][] = implode('',$tokarr);
  123. $stmtno += 1;
  124. $tokens[$stmtno] = array();
  125. $intoken = false;
  126. $tokarr = array();
  127. break;
  128. }
  129. $tokens[$stmtno][] = implode('',$tokarr);
  130. $tokens[$stmtno][] = $ch;
  131. $intoken = false;
  132. }
  133. }
  134. $pos += 1;
  135. }
  136. if ($intoken) $tokens[$stmtno][] = implode('',$tokarr);
  137. return $tokens;
  138. }
  139. class ADODB_DataDict {
  140. var $connection;
  141. var $debug = false;
  142. var $dropTable = 'DROP TABLE %s';
  143. var $renameTable = 'RENAME TABLE %s TO %s';
  144. var $dropIndex = 'DROP INDEX %s';
  145. var $addCol = ' ADD';
  146. var $alterCol = ' ALTER COLUMN';
  147. var $dropCol = ' DROP COLUMN';
  148. var $renameColumn = 'ALTER TABLE %s RENAME COLUMN %s TO %s'; // table, old-column, new-column, column-definitions (not used by default)
  149. var $nameRegex = '\w';
  150. var $nameRegexBrackets = 'a-zA-Z0-9_\(\)';
  151. var $schema = false;
  152. var $serverInfo = array();
  153. var $autoIncrement = false;
  154. var $dataProvider;
  155. var $invalidResizeTypes4 = array('CLOB','BLOB','TEXT','DATE','TIME'); // for changetablesql
  156. var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob
  157. /// in other words, we use a text area for editting.
  158. function GetCommentSQL($table,$col)
  159. {
  160. return false;
  161. }
  162. function SetCommentSQL($table,$col,$cmt)
  163. {
  164. return false;
  165. }
  166. function MetaTables()
  167. {
  168. if (!$this->connection->IsConnected()) return array();
  169. return $this->connection->MetaTables();
  170. }
  171. function MetaColumns($tab, $upper=true, $schema=false)
  172. {
  173. if (!$this->connection->IsConnected()) return array();
  174. return $this->connection->MetaColumns($this->TableName($tab), $upper, $schema);
  175. }
  176. function MetaPrimaryKeys($tab,$owner=false,$intkey=false)
  177. {
  178. if (!$this->connection->IsConnected()) return array();
  179. return $this->connection->MetaPrimaryKeys($this->TableName($tab), $owner, $intkey);
  180. }
  181. function MetaIndexes($table, $primary = false, $owner = false)
  182. {
  183. if (!$this->connection->IsConnected()) return array();
  184. return $this->connection->MetaIndexes($this->TableName($table), $primary, $owner);
  185. }
  186. function MetaType($t,$len=-1,$fieldobj=false)
  187. {
  188. static $typeMap = array(
  189. 'VARCHAR' => 'C',
  190. 'VARCHAR2' => 'C',
  191. 'CHAR' => 'C',
  192. 'C' => 'C',
  193. 'STRING' => 'C',
  194. 'NCHAR' => 'C',
  195. 'NVARCHAR' => 'C',
  196. 'VARYING' => 'C',
  197. 'BPCHAR' => 'C',
  198. 'CHARACTER' => 'C',
  199. 'INTERVAL' => 'C', # Postgres
  200. 'MACADDR' => 'C', # postgres
  201. 'VAR_STRING' => 'C', # mysql
  202. ##
  203. 'LONGCHAR' => 'X',
  204. 'TEXT' => 'X',
  205. 'NTEXT' => 'X',
  206. 'M' => 'X',
  207. 'X' => 'X',
  208. 'CLOB' => 'X',
  209. 'NCLOB' => 'X',
  210. 'LVARCHAR' => 'X',
  211. ##
  212. 'BLOB' => 'B',
  213. 'IMAGE' => 'B',
  214. 'BINARY' => 'B',
  215. 'VARBINARY' => 'B',
  216. 'LONGBINARY' => 'B',
  217. 'B' => 'B',
  218. ##
  219. 'YEAR' => 'D', // mysql
  220. 'DATE' => 'D',
  221. 'D' => 'D',
  222. ##
  223. 'UNIQUEIDENTIFIER' => 'C', # MS SQL Server
  224. ##
  225. 'TIME' => 'T',
  226. 'TIMESTAMP' => 'T',
  227. 'DATETIME' => 'T',
  228. 'TIMESTAMPTZ' => 'T',
  229. 'SMALLDATETIME' => 'T',
  230. 'T' => 'T',
  231. 'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql
  232. ##
  233. 'BOOL' => 'L',
  234. 'BOOLEAN' => 'L',
  235. 'BIT' => 'L',
  236. 'L' => 'L',
  237. ##
  238. 'COUNTER' => 'R',
  239. 'R' => 'R',
  240. 'SERIAL' => 'R', // ifx
  241. 'INT IDENTITY' => 'R',
  242. ##
  243. 'INT' => 'I',
  244. 'INT2' => 'I',
  245. 'INT4' => 'I',
  246. 'INT8' => 'I',
  247. 'INTEGER' => 'I',
  248. 'INTEGER UNSIGNED' => 'I',
  249. 'SHORT' => 'I',
  250. 'TINYINT' => 'I',
  251. 'SMALLINT' => 'I',
  252. 'I' => 'I',
  253. ##
  254. 'LONG' => 'N', // interbase is numeric, oci8 is blob
  255. 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers
  256. 'DECIMAL' => 'N',
  257. 'DEC' => 'N',
  258. 'REAL' => 'N',
  259. 'DOUBLE' => 'N',
  260. 'DOUBLE PRECISION' => 'N',
  261. 'SMALLFLOAT' => 'N',
  262. 'FLOAT' => 'N',
  263. 'NUMBER' => 'N',
  264. 'NUM' => 'N',
  265. 'NUMERIC' => 'N',
  266. 'MONEY' => 'N',
  267. ## informix 9.2
  268. 'SQLINT' => 'I',
  269. 'SQLSERIAL' => 'I',
  270. 'SQLSMINT' => 'I',
  271. 'SQLSMFLOAT' => 'N',
  272. 'SQLFLOAT' => 'N',
  273. 'SQLMONEY' => 'N',
  274. 'SQLDECIMAL' => 'N',
  275. 'SQLDATE' => 'D',
  276. 'SQLVCHAR' => 'C',
  277. 'SQLCHAR' => 'C',
  278. 'SQLDTIME' => 'T',
  279. 'SQLINTERVAL' => 'N',
  280. 'SQLBYTES' => 'B',
  281. 'SQLTEXT' => 'X',
  282. ## informix 10
  283. "SQLINT8" => 'I8',
  284. "SQLSERIAL8" => 'I8',
  285. "SQLNCHAR" => 'C',
  286. "SQLNVCHAR" => 'C',
  287. "SQLLVARCHAR" => 'X',
  288. "SQLBOOL" => 'L'
  289. );
  290. if (!$this->connection->IsConnected()) {
  291. $t = strtoupper($t);
  292. if (isset($typeMap[$t])) return $typeMap[$t];
  293. return 'N';
  294. }
  295. return $this->connection->MetaType($t,$len,$fieldobj);
  296. }
  297. function NameQuote($name = NULL,$allowBrackets=false)
  298. {
  299. if (!is_string($name)) {
  300. return FALSE;
  301. }
  302. $name = trim($name);
  303. if ( !is_object($this->connection) ) {
  304. return $name;
  305. }
  306. $quote = $this->connection->nameQuote;
  307. // if name is of the form `name`, quote it
  308. if ( preg_match('/^`(.+)`$/', $name, $matches) ) {
  309. return $quote . $matches[1] . $quote;
  310. }
  311. // if name contains special characters, quote it
  312. $regex = ($allowBrackets) ? $this->nameRegexBrackets : $this->nameRegex;
  313. if ( !preg_match('/^[' . $regex . ']+$/', $name) ) {
  314. return $quote . $name . $quote;
  315. }
  316. return $name;
  317. }
  318. function TableName($name)
  319. {
  320. if ( $this->schema ) {
  321. return $this->NameQuote($this->schema) .'.'. $this->NameQuote($name);
  322. }
  323. return $this->NameQuote($name);
  324. }
  325. // Executes the sql array returned by GetTableSQL and GetIndexSQL
  326. function ExecuteSQLArray($sql, $continueOnError = true)
  327. {
  328. $rez = 2;
  329. $conn = $this->connection;
  330. $saved = $conn->debug;
  331. foreach($sql as $line) {
  332. if ($this->debug) $conn->debug = true;
  333. $ok = $conn->Execute($line);
  334. $conn->debug = $saved;
  335. if (!$ok) {
  336. if ($this->debug) ADOConnection::outp($conn->ErrorMsg());
  337. if (!$continueOnError) return 0;
  338. $rez = 1;
  339. }
  340. }
  341. return $rez;
  342. }
  343. /**
  344. Returns the actual type given a character code.
  345. C: varchar
  346. X: CLOB (character large object) or largest varchar size if CLOB is not supported
  347. C2: Multibyte varchar
  348. X2: Multibyte CLOB
  349. B: BLOB (binary large object)
  350. D: Date
  351. T: Date-time
  352. L: Integer field suitable for storing booleans (0 or 1)
  353. I: Integer
  354. F: Floating point number
  355. N: Numeric or decimal number
  356. */
  357. function ActualType($meta)
  358. {
  359. return $meta;
  360. }
  361. function CreateDatabase($dbname,$options=false)
  362. {
  363. $options = $this->_Options($options);
  364. $sql = array();
  365. $s = 'CREATE DATABASE ' . $this->NameQuote($dbname);
  366. if (isset($options[$this->upperName]))
  367. $s .= ' '.$options[$this->upperName];
  368. $sql[] = $s;
  369. return $sql;
  370. }
  371. /*
  372. Generates the SQL to create index. Returns an array of sql strings.
  373. */
  374. function CreateIndexSQL($idxname, $tabname, $flds, $idxoptions = false)
  375. {
  376. if (!is_array($flds)) {
  377. $flds = explode(',',$flds);
  378. }
  379. foreach($flds as $key => $fld) {
  380. # some indexes can use partial fields, eg. index first 32 chars of "name" with NAME(32)
  381. $flds[$key] = $this->NameQuote($fld,$allowBrackets=true);
  382. }
  383. return $this->_IndexSQL($this->NameQuote($idxname), $this->TableName($tabname), $flds, $this->_Options($idxoptions));
  384. }
  385. function DropIndexSQL ($idxname, $tabname = NULL)
  386. {
  387. return array(sprintf($this->dropIndex, $this->NameQuote($idxname), $this->TableName($tabname)));
  388. }
  389. function SetSchema($schema)
  390. {
  391. $this->schema = $schema;
  392. }
  393. function AddColumnSQL($tabname, $flds)
  394. {
  395. $tabname = $this->TableName ($tabname);
  396. $sql = array();
  397. list($lines,$pkey,$idxs) = $this->_GenFields($flds);
  398. // genfields can return FALSE at times
  399. if ($lines == null) $lines = array();
  400. $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' ';
  401. foreach($lines as $v) {
  402. $sql[] = $alter . $v;
  403. }
  404. if (is_array($idxs)) {
  405. foreach($idxs as $idx => $idxdef) {
  406. $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
  407. $sql = array_merge($sql, $sql_idxs);
  408. }
  409. }
  410. return $sql;
  411. }
  412. /**
  413. * Change the definition of one column
  414. *
  415. * As some DBM's can't do that on there own, you need to supply the complete defintion of the new table,
  416. * to allow, recreating the table and copying the content over to the new table
  417. * @param string $tabname table-name
  418. * @param string $flds column-name and type for the changed column
  419. * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default ''
  420. * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default ''
  421. * @return array with SQL strings
  422. */
  423. function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
  424. {
  425. $tabname = $this->TableName ($tabname);
  426. $sql = array();
  427. list($lines,$pkey,$idxs) = $this->_GenFields($flds);
  428. // genfields can return FALSE at times
  429. if ($lines == null) $lines = array();
  430. $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' ';
  431. foreach($lines as $v) {
  432. $sql[] = $alter . $v;
  433. }
  434. if (is_array($idxs)) {
  435. foreach($idxs as $idx => $idxdef) {
  436. $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
  437. $sql = array_merge($sql, $sql_idxs);
  438. }
  439. }
  440. return $sql;
  441. }
  442. /**
  443. * Rename one column
  444. *
  445. * Some DBM's can only do this together with changeing the type of the column (even if that stays the same, eg. mysql)
  446. * @param string $tabname table-name
  447. * @param string $oldcolumn column-name to be renamed
  448. * @param string $newcolumn new column-name
  449. * @param string $flds='' complete column-defintion-string like for AddColumnSQL, only used by mysql atm., default=''
  450. * @return array with SQL strings
  451. */
  452. function RenameColumnSQL($tabname,$oldcolumn,$newcolumn,$flds='')
  453. {
  454. $tabname = $this->TableName ($tabname);
  455. if ($flds) {
  456. list($lines,$pkey,$idxs) = $this->_GenFields($flds);
  457. // genfields can return FALSE at times
  458. if ($lines == null) $lines = array();
  459. $first = current($lines);
  460. list(,$column_def) = preg_split("/[\t ]+/",$first,2);
  461. }
  462. return array(sprintf($this->renameColumn,$tabname,$this->NameQuote($oldcolumn),$this->NameQuote($newcolumn),$column_def));
  463. }
  464. /**
  465. * Drop one column
  466. *
  467. * Some DBM's can't do that on there own, you need to supply the complete defintion of the new table,
  468. * to allow, recreating the table and copying the content over to the new table
  469. * @param string $tabname table-name
  470. * @param string $flds column-name and type for the changed column
  471. * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default ''
  472. * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default ''
  473. * @return array with SQL strings
  474. */
  475. function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='')
  476. {
  477. $tabname = $this->TableName ($tabname);
  478. if (!is_array($flds)) $flds = explode(',',$flds);
  479. $sql = array();
  480. $alter = 'ALTER TABLE ' . $tabname . $this->dropCol . ' ';
  481. foreach($flds as $v) {
  482. $sql[] = $alter . $this->NameQuote($v);
  483. }
  484. return $sql;
  485. }
  486. function DropTableSQL($tabname)
  487. {
  488. return array (sprintf($this->dropTable, $this->TableName($tabname)));
  489. }
  490. function RenameTableSQL($tabname,$newname)
  491. {
  492. return array (sprintf($this->renameTable, $this->TableName($tabname),$this->TableName($newname)));
  493. }
  494. /**
  495. Generate the SQL to create table. Returns an array of sql strings.
  496. */
  497. function CreateTableSQL($tabname, $flds, $tableoptions=array())
  498. {
  499. list($lines,$pkey,$idxs) = $this->_GenFields($flds, true);
  500. // genfields can return FALSE at times
  501. if ($lines == null) $lines = array();
  502. $taboptions = $this->_Options($tableoptions);
  503. $tabname = $this->TableName ($tabname);
  504. $sql = $this->_TableSQL($tabname,$lines,$pkey,$taboptions);
  505. // ggiunta - 2006/10/12 - KLUDGE:
  506. // if we are on autoincrement, and table options includes REPLACE, the
  507. // autoincrement sequence has already been dropped on table creation sql, so
  508. // we avoid passing REPLACE to trigger creation code. This prevents
  509. // creating sql that double-drops the sequence
  510. if ($this->autoIncrement && isset($taboptions['REPLACE']))
  511. unset($taboptions['REPLACE']);
  512. $tsql = $this->_Triggers($tabname,$taboptions);
  513. foreach($tsql as $s) $sql[] = $s;
  514. if (is_array($idxs)) {
  515. foreach($idxs as $idx => $idxdef) {
  516. $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']);
  517. $sql = array_merge($sql, $sql_idxs);
  518. }
  519. }
  520. return $sql;
  521. }
  522. function _GenFields($flds,$widespacing=false)
  523. {
  524. if (is_string($flds)) {
  525. $padding = ' ';
  526. $txt = $flds.$padding;
  527. $flds = array();
  528. $flds0 = Lens_ParseArgs($txt,',');
  529. $hasparam = false;
  530. foreach($flds0 as $f0) {
  531. $f1 = array();
  532. foreach($f0 as $token) {
  533. switch (strtoupper($token)) {
  534. case 'INDEX':
  535. $f1['INDEX'] = '';
  536. // fall through intentionally
  537. case 'CONSTRAINT':
  538. case 'DEFAULT':
  539. $hasparam = $token;
  540. break;
  541. default:
  542. if ($hasparam) $f1[$hasparam] = $token;
  543. else $f1[] = $token;
  544. $hasparam = false;
  545. break;
  546. }
  547. }
  548. // 'index' token without a name means single column index: name it after column
  549. if (array_key_exists('INDEX', $f1) && $f1['INDEX'] == '') {
  550. $f1['INDEX'] = isset($f0['NAME']) ? $f0['NAME'] : $f0[0];
  551. // check if column name used to create an index name was quoted
  552. if (($f1['INDEX'][0] == '"' || $f1['INDEX'][0] == "'" || $f1['INDEX'][0] == "`") &&
  553. ($f1['INDEX'][0] == substr($f1['INDEX'], -1))) {
  554. $f1['INDEX'] = $f1['INDEX'][0].'idx_'.substr($f1['INDEX'], 1, -1).$f1['INDEX'][0];
  555. }
  556. else
  557. $f1['INDEX'] = 'idx_'.$f1['INDEX'];
  558. }
  559. // reset it, so we don't get next field 1st token as INDEX...
  560. $hasparam = false;
  561. $flds[] = $f1;
  562. }
  563. }
  564. $this->autoIncrement = false;
  565. $lines = array();
  566. $pkey = array();
  567. $idxs = array();
  568. foreach($flds as $fld) {
  569. $fld = _array_change_key_case($fld);
  570. $fname = false;
  571. $fdefault = false;
  572. $fautoinc = false;
  573. $ftype = false;
  574. $fsize = false;
  575. $fprec = false;
  576. $fprimary = false;
  577. $fnoquote = false;
  578. $fdefts = false;
  579. $fdefdate = false;
  580. $fconstraint = false;
  581. $fnotnull = false;
  582. $funsigned = false;
  583. $findex = '';
  584. $funiqueindex = false;
  585. //-----------------
  586. // Parse attributes
  587. foreach($fld as $attr => $v) {
  588. if ($attr == 2 && is_numeric($v)) $attr = 'SIZE';
  589. else if (is_numeric($attr) && $attr > 1 && !is_numeric($v)) $attr = strtoupper($v);
  590. switch($attr) {
  591. case '0':
  592. case 'NAME': $fname = $v; break;
  593. case '1':
  594. case 'TYPE': $ty = $v; $ftype = $this->ActualType(strtoupper($v)); break;
  595. case 'SIZE':
  596. $dotat = strpos($v,'.'); if ($dotat === false) $dotat = strpos($v,',');
  597. if ($dotat === false) $fsize = $v;
  598. else {
  599. $fsize = substr($v,0,$dotat);
  600. $fprec = substr($v,$dotat+1);
  601. }
  602. break;
  603. case 'UNSIGNED': $funsigned = true; break;
  604. case 'AUTOINCREMENT':
  605. case 'AUTO': $fautoinc = true; $fnotnull = true; break;
  606. case 'KEY':
  607. // a primary key col can be non unique in itself (if key spans many cols...)
  608. case 'PRIMARY': $fprimary = $v; $fnotnull = true; /*$funiqueindex = true;*/ break;
  609. case 'DEF':
  610. case 'DEFAULT': $fdefault = $v; break;
  611. case 'NOTNULL': $fnotnull = $v; break;
  612. case 'NOQUOTE': $fnoquote = $v; break;
  613. case 'DEFDATE': $fdefdate = $v; break;
  614. case 'DEFTIMESTAMP': $fdefts = $v; break;
  615. case 'CONSTRAINT': $fconstraint = $v; break;
  616. // let INDEX keyword create a 'very standard' index on column
  617. case 'INDEX': $findex = $v; break;
  618. case 'UNIQUE': $funiqueindex = true; break;
  619. } //switch
  620. } // foreach $fld
  621. //--------------------
  622. // VALIDATE FIELD INFO
  623. if (!strlen($fname)) {
  624. if ($this->debug) ADOConnection::outp("Undefined NAME");
  625. return false;
  626. }
  627. $fid = strtoupper(preg_replace('/^`(.+)`$/', '$1', $fname));
  628. $fname = $this->NameQuote($fname);
  629. if (!strlen($ftype)) {
  630. if ($this->debug) ADOConnection::outp("Undefined TYPE for field '$fname'");
  631. return false;
  632. } else {
  633. $ftype = strtoupper($ftype);
  634. }
  635. $ftype = $this->_GetSize($ftype, $ty, $fsize, $fprec);
  636. if ($ty == 'X' || $ty == 'X2' || $ty == 'B') $fnotnull = false; // some blob types do not accept nulls
  637. if ($fprimary) $pkey[] = $fname;
  638. // some databases do not allow blobs to have defaults
  639. if ($ty == 'X') $fdefault = false;
  640. // build list of indexes
  641. if ($findex != '') {
  642. if (array_key_exists($findex, $idxs)) {
  643. $idxs[$findex]['cols'][] = ($fname);
  644. if (in_array('UNIQUE', $idxs[$findex]['opts']) != $funiqueindex) {
  645. if ($this->debug) ADOConnection::outp("Index $findex defined once UNIQUE and once not");
  646. }
  647. if ($funiqueindex && !in_array('UNIQUE', $idxs[$findex]['opts']))
  648. $idxs[$findex]['opts'][] = 'UNIQUE';
  649. }
  650. else
  651. {
  652. $idxs[$findex] = array();
  653. $idxs[$findex]['cols'] = array($fname);
  654. if ($funiqueindex)
  655. $idxs[$findex]['opts'] = array('UNIQUE');
  656. else
  657. $idxs[$findex]['opts'] = array();
  658. }
  659. }
  660. //--------------------
  661. // CONSTRUCT FIELD SQL
  662. if ($fdefts) {
  663. if (substr($this->connection->databaseType,0,5) == 'mysql') {
  664. $ftype = 'TIMESTAMP';
  665. } else {
  666. $fdefault = $this->connection->sysTimeStamp;
  667. }
  668. } else if ($fdefdate) {
  669. if (substr($this->connection->databaseType,0,5) == 'mysql') {
  670. $ftype = 'TIMESTAMP';
  671. } else {
  672. $fdefault = $this->connection->sysDate;
  673. }
  674. } else if ($fdefault !== false && !$fnoquote) {
  675. if ($ty == 'C' or $ty == 'X' or
  676. ( substr($fdefault,0,1) != "'" && !is_numeric($fdefault))) {
  677. if (($ty == 'D' || $ty == 'T') && strtolower($fdefault) != 'null') {
  678. // convert default date into database-aware code
  679. if ($ty == 'T')
  680. {
  681. $fdefault = $this->connection->DBTimeStamp($fdefault);
  682. }
  683. else
  684. {
  685. $fdefault = $this->connection->DBDate($fdefault);
  686. }
  687. }
  688. else
  689. if (strlen($fdefault) != 1 && substr($fdefault,0,1) == ' ' && substr($fdefault,strlen($fdefault)-1) == ' ')
  690. $fdefault = trim($fdefault);
  691. else if (strtolower($fdefault) != 'null')
  692. $fdefault = $this->connection->qstr($fdefault);
  693. }
  694. }
  695. $suffix = $this->_CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned);
  696. // add index creation
  697. if ($widespacing) $fname = str_pad($fname,24);
  698. // check for field names appearing twice
  699. if (array_key_exists($fid, $lines)) {
  700. ADOConnection::outp("Field '$fname' defined twice");
  701. }
  702. $lines[$fid] = $fname.' '.$ftype.$suffix;
  703. if ($fautoinc) $this->autoIncrement = true;
  704. } // foreach $flds
  705. return array($lines,$pkey,$idxs);
  706. }
  707. /**
  708. GENERATE THE SIZE PART OF THE DATATYPE
  709. $ftype is the actual type
  710. $ty is the type defined originally in the DDL
  711. */
  712. function _GetSize($ftype, $ty, $fsize, $fprec)
  713. {
  714. if (strlen($fsize) && $ty != 'X' && $ty != 'B' && strpos($ftype,'(') === false) {
  715. $ftype .= "(".$fsize;
  716. if (strlen($fprec)) $ftype .= ",".$fprec;
  717. $ftype .= ')';
  718. }
  719. return $ftype;
  720. }
  721. // return string must begin with space
  722. function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned)
  723. {
  724. $suffix = '';
  725. if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault";
  726. if ($fnotnull) $suffix .= ' NOT NULL';
  727. if ($fconstraint) $suffix .= ' '.$fconstraint;
  728. return $suffix;
  729. }
  730. function _IndexSQL($idxname, $tabname, $flds, $idxoptions)
  731. {
  732. $sql = array();
  733. if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) {
  734. $sql[] = sprintf ($this->dropIndex, $idxname);
  735. if ( isset($idxoptions['DROP']) )
  736. return $sql;
  737. }
  738. if ( empty ($flds) ) {
  739. return $sql;
  740. }
  741. $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : '';
  742. $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' ';
  743. if ( isset($idxoptions[$this->upperName]) )
  744. $s .= $idxoptions[$this->upperName];
  745. if ( is_array($flds) )
  746. $flds = implode(', ',$flds);
  747. $s .= '(' . $flds . ')';
  748. $sql[] = $s;
  749. return $sql;
  750. }
  751. function _DropAutoIncrement($tabname)
  752. {
  753. return false;
  754. }
  755. function _TableSQL($tabname,$lines,$pkey,$tableoptions)
  756. {
  757. $sql = array();
  758. if (isset($tableoptions['REPLACE']) || isset ($tableoptions['DROP'])) {
  759. $sql[] = sprintf($this->dropTable,$tabname);
  760. if ($this->autoIncrement) {
  761. $sInc = $this->_DropAutoIncrement($tabname);
  762. if ($sInc) $sql[] = $sInc;
  763. }
  764. if ( isset ($tableoptions['DROP']) ) {
  765. return $sql;
  766. }
  767. }
  768. $s = "CREATE TABLE $tabname (\n";
  769. $s .= implode(",\n", $lines);
  770. if (sizeof($pkey)>0) {
  771. $s .= ",\n PRIMARY KEY (";
  772. $s .= implode(", ",$pkey).")";
  773. }
  774. if (isset($tableoptions['CONSTRAINTS']))
  775. $s .= "\n".$tableoptions['CONSTRAINTS'];
  776. if (isset($tableoptions[$this->upperName.'_CONSTRAINTS']))
  777. $s .= "\n".$tableoptions[$this->upperName.'_CONSTRAINTS'];
  778. $s .= "\n)";
  779. if (isset($tableoptions[$this->upperName])) $s .= $tableoptions[$this->upperName];
  780. $sql[] = $s;
  781. return $sql;
  782. }
  783. /**
  784. GENERATE TRIGGERS IF NEEDED
  785. used when table has auto-incrementing field that is emulated using triggers
  786. */
  787. function _Triggers($tabname,$taboptions)
  788. {
  789. return array();
  790. }
  791. /**
  792. Sanitize options, so that array elements with no keys are promoted to keys
  793. */
  794. function _Options($opts)
  795. {
  796. if (!is_array($opts)) return array();
  797. $newopts = array();
  798. foreach($opts as $k => $v) {
  799. if (is_numeric($k)) $newopts[strtoupper($v)] = $v;
  800. else $newopts[strtoupper($k)] = $v;
  801. }
  802. return $newopts;
  803. }
  804. function _getSizePrec($size)
  805. {
  806. $fsize = false;
  807. $fprec = false;
  808. $dotat = strpos($size,'.');
  809. if ($dotat === false) $dotat = strpos($size,',');
  810. if ($dotat === false) $fsize = $size;
  811. else {
  812. $fsize = substr($size,0,$dotat);
  813. $fprec = substr($size,$dotat+1);
  814. }
  815. return array($fsize, $fprec);
  816. }
  817. /**
  818. "Florian Buzin [ easywe ]" <florian.buzin#easywe.de>
  819. This function changes/adds new fields to your table. You don't
  820. have to know if the col is new or not. It will check on its own.
  821. */
  822. function ChangeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false)
  823. {
  824. global $ADODB_FETCH_MODE;
  825. $save = $ADODB_FETCH_MODE;
  826. $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
  827. if ($this->connection->fetchMode !== false) $savem = $this->connection->SetFetchMode(false);
  828. // check table exists
  829. $save_handler = $this->connection->raiseErrorFn;
  830. $this->connection->raiseErrorFn = '';
  831. $cols = $this->MetaColumns($tablename);
  832. $this->connection->raiseErrorFn = $save_handler;
  833. if (isset($savem)) $this->connection->SetFetchMode($savem);
  834. $ADODB_FETCH_MODE = $save;
  835. if ( empty($cols)) {
  836. return $this->CreateTableSQL($tablename, $flds, $tableoptions);
  837. }
  838. if (is_array($flds)) {
  839. // Cycle through the update fields, comparing
  840. // existing fields to fields to update.
  841. // if the Metatype and size is exactly the
  842. // same, ignore - by Mark Newham
  843. $holdflds = array();
  844. foreach($flds as $k=>$v) {
  845. if ( isset($cols[$k]) && is_object($cols[$k]) ) {
  846. // If already not allowing nulls, then don't change
  847. $obj = $cols[$k];
  848. if (isset($obj->not_null) && $obj->not_null)
  849. $v = str_replace('NOT NULL','',$v);
  850. if (isset($obj->auto_increment) && $obj->auto_increment && empty($v['AUTOINCREMENT']))
  851. $v = str_replace('AUTOINCREMENT','',$v);
  852. $c = $cols[$k];
  853. $ml = $c->max_length;
  854. $mt = $this->MetaType($c->type,$ml);
  855. if (isset($c->scale)) $sc = $c->scale;
  856. else $sc = 99; // always force change if scale not known.
  857. if ($sc == -1) $sc = false;
  858. list($fsize, $fprec) = $this->_getSizePrec($v['SIZE']);
  859. if ($ml == -1) $ml = '';
  860. if ($mt == 'X') $ml = $v['SIZE'];
  861. if (($mt != $v['TYPE']) || ($ml != $fsize || $sc != $fprec) || (isset($v['AUTOINCREMENT']) && $v['AUTOINCREMENT'] != $obj->auto_increment)) {
  862. $holdflds[$k] = $v;
  863. }
  864. } else {
  865. $holdflds[$k] = $v;
  866. }
  867. }
  868. $flds = $holdflds;
  869. }
  870. // already exists, alter table instead
  871. list($lines,$pkey,$idxs) = $this->_GenFields($flds);
  872. // genfields can return FALSE at times
  873. if ($lines == null) $lines = array();
  874. $alter = 'ALTER TABLE ' . $this->TableName($tablename);
  875. $sql = array();
  876. foreach ( $lines as $id => $v ) {
  877. if ( isset($cols[$id]) && is_object($cols[$id]) ) {
  878. $flds = Lens_ParseArgs($v,',');
  879. // We are trying to change the size of the field, if not allowed, simply ignore the request.
  880. // $flds[1] holds the type, $flds[2] holds the size -postnuke addition
  881. if ($flds && in_array(strtoupper(substr($flds[0][1],0,4)),$this->invalidResizeTypes4)
  882. && (isset($flds[0][2]) && is_numeric($flds[0][2]))) {
  883. if ($this->debug) ADOConnection::outp(sprintf("<h3>%s cannot be changed to %s currently</h3>", $flds[0][0], $flds[0][1]));
  884. #echo "<h3>$this->alterCol cannot be changed to $flds currently</h3>";
  885. continue;
  886. }
  887. $sql[] = $alter . $this->alterCol . ' ' . $v;
  888. } else {
  889. $sql[] = $alter . $this->addCol . ' ' . $v;
  890. }
  891. }
  892. if ($dropOldFlds) {
  893. foreach ( $cols as $id => $v )
  894. if ( !isset($lines[$id]) )
  895. $sql[] = $alter . $this->dropCol . ' ' . $v->name;
  896. }
  897. return $sql;
  898. }
  899. } // class