Pdo.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. <?php
  2. Lib::loadClass('DBG');
  3. class Core_Pdo extends PDO {
  4. protected $_database_name;
  5. protected $_zasob_id;
  6. protected $_type;
  7. // public PDO::__construct ( string $dsn [, string $username [, string $password [, array $options ]]] )
  8. public function __construct($dsn, $username, $password, $options = array()) {
  9. $this->_database_name = $options['database'];
  10. $this->_zasob_id = $options['zasob_id'];
  11. $this->_type = $options['type'];
  12. unset($options['database']);
  13. unset($options['zasob_id']);
  14. parent::__construct($dsn, $username, $password, $options);
  15. $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  16. $this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
  17. }
  18. public function getDatabaseName() {
  19. return $this->_database_name;
  20. }
  21. public function getZasobId() {
  22. return $this->_zasob_id;
  23. }
  24. public function getType() {
  25. return strtolower($this->_type);
  26. }
  27. public function identifierQuote($identifier) {
  28. switch (strtolower($this->_type)) {
  29. case 'pgsql': return $identifier; // "'{$identifier}'";
  30. case 'mysql': return "`{$identifier}`";
  31. }
  32. return $identifier;
  33. }
  34. public function getTableStruct($tblName) {// TODO: mved to Core_Storage_*
  35. $sth = $this->prepare("
  36. -- show fields from {$tblName}
  37. select cols.COLUMN_NAME as name
  38. , cols.DATA_TYPE as type
  39. , cols.COLUMN_TYPE as type_mysql
  40. , if('YES' = cols.IS_NULLABLE, 1, 0) as is_nullable
  41. , cols.COLUMN_DEFAULT as default_value
  42. , if(cols.COLUMN_DEFAULT is null, 1, 0) as default_is_null
  43. , cols.CHARACTER_MAXIMUM_LENGTH as max_length
  44. , cols.NUMERIC_PRECISION as num_precision
  45. , cols.NUMERIC_SCALE as num_scale
  46. , cols.CHARACTER_SET_NAME as char_encoding -- latin2
  47. , cols.COLLATION_NAME as char_collation -- latin2_general_ci
  48. , cols.EXTRA as extra
  49. -- , cols.*
  50. from INFORMATION_SCHEMA.COLUMNS cols
  51. where cols.TABLE_SCHEMA = :db_name
  52. and cols.TABLE_NAME = :tbl_name
  53. ");
  54. $sth->bindValue(':db_name', $this->getDatabaseName(), PDO::PARAM_STR);
  55. $sth->bindValue(':tbl_name', $tblName, PDO::PARAM_STR);
  56. $sth->execute();
  57. $structRaw = $sth->fetchAll();
  58. if (empty($structRaw)) throw new Exception("Empty struct for table '{$tblName}'", 404);
  59. foreach ($structRaw as $field) {
  60. $struct[$field['name']] = $field;
  61. }
  62. return $struct;
  63. }
  64. public function assertTableStructXsd($tblName, $expectedStructXsd) {
  65. throw new Exception("Unimplemented - TODO!");
  66. /*
  67. - `decimal(5,2)`:
  68. <xsd:element name="A" type="decimal_5_2"/>
  69. <xsd:simpleType name="decimal_5_2">
  70. <xsd:restriction base="xsd:decimal">
  71. <xsd:totalDigits value="5"/>
  72. <xsd:fractionDigits value="2"/>
  73. </xsd:restriction>
  74. </xsd:simpleType>
  75. */
  76. /* MySQL types:
  77. int tinyint smallint mediumint bigint
  78. decimal
  79. float double real => double
  80. date datetime timestamp time year
  81. char varchar
  82. text tinytext mediumtext longtext
  83. enum
  84. set
  85. bit
  86. boolean => `tinyint(1)` -- 0 or 1
  87. serial => `bigint(20) unsigned` and unique key
  88. binary varbinary
  89. blob tinyblob mediumblob longblob
  90. geometry point linestring polygon multipoint multilinestring multipolygon geometrycollection
  91. */
  92. }
  93. /*
  94. * TODO: update keys:
  95. * TODO: keys name may be different - try to find and connect with given schema?
  96. * TODO: remove old uniq keys?
  97. */
  98. public function assertTableStruct($tblName, $expectedStruct, $params = array()) {
  99. // TODO: make backup for table?
  100. $expectedStruct = $this->_fixExpectedStruct($expectedStruct);
  101. //DBG::_(true, true, "fixedEpectedStruct", $expectedStruct, __CLASS__, __FUNCTION__, __LINE__);
  102. //DBG::_(true, true, "fixedEpectedStruct", $this->showCreateStruct($tblName, $expectedStruct, $params), __CLASS__, __FUNCTION__, __LINE__);
  103. $struct = $this->getTableStruct($tblName);
  104. $expectedFields = array();//array_keys($expectedStruct);
  105. foreach ($expectedStruct as $fldName => $fld) {
  106. if ('UNIQUE KEY' == $fld['type']) continue;
  107. if ('KEY' == $fld['type']) continue;
  108. $expectedFields[] = $fldName;
  109. }
  110. $currentFields = array_keys($struct);
  111. $missingFields = array_diff($expectedFields, $currentFields);
  112. DBG::_(true, true, "struct", $struct, __CLASS__, __FUNCTION__, __LINE__);
  113. DBG::_(true, true, "missingFields", $missingFields, __CLASS__, __FUNCTION__, __LINE__);
  114. foreach ($missingFields as $fldName) {
  115. $fld = $expectedStruct[$fldName];
  116. DBG::_(true, true, "add missing field[{$fldName}]:", $fld, __CLASS__, __FUNCTION__, __LINE__);
  117. $sqlFieldStruct = $this->showTableStructField($fldName, $fld);
  118. if ($sqlFieldStruct) {
  119. $sqlAdd = "alter table {$tblName} add {$sqlFieldStruct}";
  120. DBG::_(true, true, "sqlAdd", $sqlAdd, __CLASS__, __FUNCTION__, __LINE__);
  121. $this->exec($sqlAdd);
  122. } else {
  123. throw new Exception("Unimplemented type '{$fld['type']}': " . json_encode($fld));
  124. }
  125. }
  126. $toUpdateFields = array_intersect($expectedFields, $currentFields);
  127. DBG::_(true, true, "toUpdateFields", $toUpdateFields, __CLASS__, __FUNCTION__, __LINE__);
  128. foreach ($toUpdateFields as $fldName) {
  129. $current = $struct[$fldName];
  130. $expected = $expectedStruct[$fldName];
  131. $needChange = false;
  132. $sqlFieldStruct = $this->showTableStructField($fldName, $expected);
  133. if (!$sqlFieldStruct) throw new Exception("Unimplemented type '{$expected['type']}' for field '{$fldName}': " . json_encode($expected));
  134. $sqlChange = "alter table `{$tblName}` change `{$fldName}` {$sqlFieldStruct}";
  135. DBG::_(true, true, "DBG: sqlChange", $sqlChange, __CLASS__, __FUNCTION__, __LINE__);
  136. //DBG::_(true, true, "TODO: update field[{$fldName}]:", array('expected'=>$expected,'current'=>$current), __CLASS__, __FUNCTION__, __LINE__);
  137. if ($current['type'] != $expected['type']) {
  138. throw new Exception("Unimplemented change field type from '{$current['type']}' to '{$expected['type']}' for field '{$fldName}': " . json_encode($expected));
  139. }
  140. if ($current['is_nullable'] != $expected['is_nullable']) {
  141. $needChange = true;
  142. if ($current['is_nullable'] && !$expected['is_nullable']) {
  143. throw new Exception("Field struct needs change 'is_nullable' to false but this change is not implemented - field '{$fldName}': " . json_encode(array('expected'=>$expected,'current'=>$current)));
  144. }
  145. }
  146. if ($current['max_length'] != $expected['max_length']) {
  147. $needChange = true;
  148. if ($current['max_length'] > $expected['max_length']) {
  149. throw new Exception("Field struct needs decrease 'max_length' but this change is not implemented - field '{$fldName}': " . json_encode(array('expected'=>$expected,'current'=>$current)));
  150. }
  151. }
  152. if ($current['default_value'] !== $expected['default_value']) $needChange = true;
  153. if ($needChange) {
  154. DBG::_(true, true, "EXEC sqlChange ...", $sqlChange, __CLASS__, __FUNCTION__, __LINE__);
  155. $this->exec($sqlChange);
  156. }
  157. }
  158. }
  159. /* assert's that field struct has defined or throw exception if cannot set default value:
  160. ['type'] = 'varchar', 'int', ... (MySQL Types)
  161. ['is_nullable'] = true, false
  162. ['default_value'] = NULL or (string, numeric - based on type)
  163. default_value is not set when default_value == NULL and is_nullable == false
  164. ['default_is_null'] = true, false
  165. ['max_length'] = NULL, [0-...] // MySQL `CHARACTER_MAXIMUM_LENGTH`
  166. ['num_precision'] = NULL, [0-65]
  167. ['num_scale'] = NULL, [0-12]
  168. ['char_encoding'] = 'utf8', 'latin2', ...
  169. ['char_collation'] = 'utf8_general_ci', 'latin2_general_ci', ...
  170. ['extra'] = 'auto_increment', 'on update CURRENT_TIMESTAMP'
  171. ['values'] = null or array for enum and set
  172. TODO: validate - wrong related values. ex: {type: 'int', char_collation: 'latin2'}
  173. TODO: validate - not allowed value. ex: {type: 'xyz'}
  174. */
  175. public function _fixExpectedStruct($expectedStruct) {
  176. $fixedStruct = array();
  177. foreach ($expectedStruct as $fldName => $expected) {
  178. //DBG::_(true, true, "TODO: expected", $expected, __CLASS__, __FUNCTION__, __LINE__);
  179. if (!array_key_exists('type', $expected)) throw new Exception("Undefined type for field '{$fldName}'");
  180. if (!array_key_exists('is_nullable', $expected)) {
  181. $expected['is_nullable'] = false;
  182. if (array_key_exists('default_value', $expected) && null === $expected['default_value']) {
  183. $expected['is_nullable'] = true;
  184. }
  185. }
  186. if (!array_key_exists('default_is_null', $expected)) $expected['default_is_null'] = false;
  187. if (!array_key_exists('default_value', $expected)) {
  188. $expected['default_value'] = null;
  189. // switch ($expected['type']) {
  190. // case 'char':
  191. // case 'varchar': $expected['default_value'] = ($expected['is_nullable'])? null : ''; break;
  192. // case 'tinyint':
  193. // case 'bigint':
  194. // case 'int': $expected['default_value'] = ($expected['is_nullable'])? null : 0; break;
  195. // }
  196. if ($expected['is_nullable'] && null === $expected['default_value']) {
  197. $expected['default_is_null'] = true;
  198. }
  199. }
  200. if (!array_key_exists('max_length', $expected)) {
  201. switch ($expected['type']) {
  202. case 'char':
  203. case 'varchar': $expected['max_length'] = 255; break;
  204. case 'binary': $expected['max_length'] = 255; break;// bainary(0) is possible - why? Cannot store values.
  205. case 'varbinary': $expected['max_length'] = 255; break;
  206. case 'binary':
  207. case 'varbinary':
  208. case 'bit': throw new Exception("Undefined max_length for field '{$fldName}' with type '{$expected['type']}'");
  209. //case 'blob': $expected['max_length'] = 65535; break;// is set by engine
  210. //case 'tinyblob': $expected['max_length'] = 255; break;// is set by engine
  211. //case 'mediumblob': $expected['max_length'] = 16777215; break;// is set by engine
  212. //case 'longblob': $expected['max_length'] = 4294967295; break;// is set by engine
  213. //case 'text': $expected['max_length'] = 65535; break;// is set by engine
  214. //case 'tinytext': $expected['max_length'] = 255; break;// is set by engine
  215. //case 'mediumtext': $expected['max_length'] = 16777215; break;// is set by engine
  216. //case 'longtext': $expected['max_length'] = 4294967295; break;// is set by engine
  217. //case 'enum': $expected['max_length'] = 255; break;// is set by engine
  218. //case 'set': $expected['max_length'] = 255; break;// is set by engine
  219. default: $expected['max_length'] = null;//throw new Exception("Undefined max_length for field '{$fldName}'");
  220. }
  221. }
  222. if (!array_key_exists('num_precision', $expected)) {
  223. switch ($expected['type']) {
  224. case 'int': $expected['num_precision'] = 10; break;// int(11) - +1 in type definition
  225. case 'tinyint': $expected['num_precision'] = 3; break;// int(4)
  226. case 'smallint': $expected['num_precision'] = 5; break;// int(6)
  227. case 'mediumint': $expected['num_precision'] = 7; break;// int(8)
  228. case 'bigint': $expected['num_precision'] = 19; break;// int(20)
  229. case 'decimal': $expected['num_precision'] = 10; break;// decimal(10,0)
  230. default: $expected['num_precision'] = null;
  231. }
  232. //throw new Exception("Undefined num_precision for field '{$fldName}'");
  233. }
  234. if (!array_key_exists('num_scale', $expected)) {
  235. switch ($expected['type']) {
  236. case 'int': $expected['num_scale'] = 0; break;
  237. case 'tinyint': $expected['num_scale'] = 0; break;
  238. case 'smallint': $expected['num_scale'] = 0; break;
  239. case 'mediumint': $expected['num_scale'] = 0; break;
  240. case 'bigint': $expected['num_scale'] = 0; break;
  241. case 'decimal': $expected['num_scale'] = 0; break;// ex.: decimal(3,2); decimal(24,0)
  242. case 'double': $expected['num_scale'] = null; break;// ex.: double(10,4); double; double(17,0);
  243. case 'float': $expected['num_scale'] = null; break;// ex.: float(6,2); float; float(17,0);
  244. default: $expected['num_scale'] = null;
  245. }
  246. //throw new Exception("Undefined num_scale for field '{$fldName}'");
  247. }
  248. if (!array_key_exists('char_encoding', $expected)) {
  249. switch ($expected['type']) {
  250. case 'char':
  251. case 'varchar': $expected['char_encoding'] = 'utf8'; break;
  252. case 'enum':
  253. case 'set': $expected['char_encoding'] = 'utf8'; break;
  254. case 'text':
  255. case 'tinytext':
  256. case 'mediumtext':
  257. case 'longtext': $expected['char_encoding'] = 'utf8'; break;
  258. default: $expected['char_encoding'] = null;
  259. }
  260. //throw new Exception("Undefined char_encoding for field '{$fldName}'");
  261. }
  262. if (!array_key_exists('char_collation', $expected)) {
  263. switch ($expected['type']) {
  264. case 'char':
  265. case 'varchar': $expected['char_collation'] = 'utf8_general_ci'; break;
  266. case 'enum':
  267. case 'set': $expected['char_collation'] = 'utf8_general_ci'; break;
  268. case 'text':
  269. case 'tinytext':
  270. case 'mediumtext':
  271. case 'longtext': $expected['char_collation'] = 'utf8_general_ci'; break;
  272. default: $expected['char_collation'] = null;
  273. }
  274. //throw new Exception("Undefined char_collation for field '{$fldName}'");
  275. }
  276. if (!array_key_exists('extra', $expected)) {
  277. $expected['extra'] = null;//throw new Exception("Undefined extra for field '{$fldName}'");
  278. }
  279. if (!array_key_exists('values', $expected) || !is_array($expected['values']) || empty($expected['values'])) {
  280. switch ($expected['type']) {
  281. case 'enum':
  282. case 'set': throw new Exception("Undefined values for field '{$fldName}' with type '{$expected['type']}'");
  283. default: $expected['values'] = null;
  284. }
  285. }
  286. $fixedStruct[$fldName] = $expected;
  287. }
  288. return $fixedStruct;
  289. }
  290. public function showCreateStruct($tblName, $struct, $params = array()) {
  291. // TODO: check database type, if == MySQL - fix construct
  292. $expectedStruct = $this->_fixExpectedStruct($struct);
  293. $linesSql = array();
  294. $dbgSql = array();
  295. foreach ($expectedStruct as $fldName => $fld) {
  296. $sqlFieldStruct = $this->showTableStructField($fldName, $fld);
  297. if ($sqlFieldStruct) {
  298. $linesSql[] = $sqlFieldStruct;
  299. } else {
  300. $dbgSql[] = "-- Unimplemented type '{$fld['type']}': " . json_encode($fld);
  301. }
  302. }
  303. $linesSql = implode("\n\t\t, ", $linesSql);
  304. $dbgSql = implode("\n\t\t", $dbgSql);
  305. $tblCharEncoding = V::get('char_encoding', 'utf8', $params);
  306. $structSql = <<<EOF_STRUCT_MYSQL
  307. CREATE TABLE IF NOT EXISTS `{$tblName}` (
  308. {$linesSql}
  309. {$dbgSql}
  310. ) ENGINE=MyISAM DEFAULT CHARSET={$tblCharEncoding}
  311. EOF_STRUCT_MYSQL;
  312. return $structSql;
  313. }
  314. public function showTableStructField($fldName, $fld) {
  315. // TODO: check database type, if == MySQL - fix construct
  316. $nullSql = ($fld['is_nullable'])? '' : 'NOT NULL';
  317. $defaultSql = (is_null($fld['default_value']))? 'DEFAULT NULL' : "DEFAULT '{$fld['default_value']}'";
  318. if (is_null($fld['default_value']) && !$fld['is_nullable']) $defaultSql = '';
  319. switch ($fld['type']) {
  320. case 'char':
  321. case 'varchar': return "`{$fldName}` {$fld['type']}({$fld['max_length']}) {$nullSql} {$defaultSql}"; break;
  322. case 'text':
  323. case 'tinytext':
  324. case 'longtext':
  325. case 'mediumtext': return "`{$fldName}` {$fld['type']} {$nullSql}"; break;
  326. case 'time':
  327. case 'timestamp':
  328. case 'year':
  329. case 'date':
  330. case 'datetime': return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}"; break;
  331. // -- Unimplemented type 'int': {"type":"int","is_nullable":false,"default_is_null":false,"default_value":null,"max_length":null,"num_precision":10,"num_scale":0,"char_encoding":null,"char_collation":null,"extra":null}
  332. // -- Unimplemented type 'int': {"type":"int","num_precision":11,"default_value":null,"is_nullable":true,"default_is_null":false,"max_length":null,"num_scale":0,"char_encoding":null,"char_collation":null,"extra":null}
  333. case 'int':
  334. case 'tinyint':
  335. case 'smallint':
  336. case 'mediumint':
  337. case 'bigint':
  338. if ($fld['num_precision'] > 0) {
  339. $typeParamsSql = "(" . ($fld['num_precision'] + 1) . ")";
  340. }
  341. //if ($fld['num_scale']) $typeParamsSql = ",{$fld['num_scale']}";
  342. return "`{$fldName}` {$fld['type']}{$typeParamsSql} {$nullSql} {$defaultSql}";
  343. break;
  344. case 'decimal':
  345. $typeParamsSql = "{$fld['num_precision']},{$fld['num_scale']}";
  346. return "`{$fldName}` {$fld['type']}({$typeParamsSql}) {$nullSql} {$defaultSql}";
  347. break;
  348. case 'float':
  349. case 'double':
  350. case 'real':
  351. return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
  352. break;
  353. case 'enum':
  354. case 'set':
  355. $typeParamsSql = "'" . implode("','", $fld['values']) . "'";
  356. return "`{$fldName}` {$fld['type']}({$typeParamsSql}) {$nullSql} {$defaultSql}";
  357. break;
  358. case 'bit':
  359. case 'binary':
  360. case 'varbinary':
  361. return "`{$fldName}` {$fld['type']}({$fld['max_length']}) {$nullSql} {$defaultSql}";
  362. break;
  363. case 'boolean':
  364. case 'serial':
  365. case 'blob':
  366. case 'tinyblob':
  367. case 'mediumblob':
  368. case 'longblob':
  369. return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
  370. break;
  371. case 'geometry':
  372. case 'point':
  373. case 'linestring':
  374. case 'polygon':
  375. case 'multipoint':
  376. case 'multilinestring':
  377. case 'multipolygon':
  378. case 'geometrycollection':
  379. return "`{$fldName}` {$fld['type']} {$nullSql} {$defaultSql}";
  380. break;
  381. case 'UNIQUE KEY':
  382. $keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
  383. return "UNIQUE KEY `{$fldName}` ({$keyFieldsSql})";
  384. break;
  385. case 'KEY':
  386. $keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
  387. return "KEY `{$fldName}` ({$keyFieldsSql})";
  388. break;
  389. case 'PRIMARY KEY':
  390. $keyFieldsSql = "`" . implode("`,`", $fld['key_fields']) . "`";
  391. return "PRIMARY KEY ({$keyFieldsSql})";
  392. break;
  393. }
  394. return null;
  395. }
  396. public function fetchValue($sql, $values = []) { // for sql like `select count() from ...`
  397. $sth = $this->prepare($sql);
  398. if (!empty($values)) {
  399. $this->bindValues($sth, $values);
  400. DBG::log($this->getRawSql($sth), 'sql');
  401. } else {
  402. DBG::log($sql, 'sql');
  403. }
  404. $sth->execute();
  405. return $sth->fetchColumn();
  406. }
  407. public function fetchValuesList($sql, $values = []) { // for sql like `select ID from ...` @returns array of ID
  408. return array_map(function ($row) {
  409. return reset($row);
  410. }, $this->fetchAll($sql, $values));
  411. }
  412. public function fetchFirst($sql, $values = []) { // fetch only first row
  413. $sth = $this->prepare($sql);
  414. if (!empty($values)) {
  415. $this->bindValues($sth, $values);
  416. DBG::log($this->getRawSql($sth), 'sql');
  417. } else {
  418. DBG::log($sql, 'sql');
  419. }
  420. $sth->execute();
  421. return $sth->fetch();
  422. }
  423. public function fetchFirstNoLog($sql, $values = []) { // fetch only first row - used in User::getID() required in DBG
  424. $sth = $this->prepare($sql);
  425. if (!empty($values)) {
  426. $this->bindValues($sth, $values);
  427. }
  428. $sth->execute();
  429. return $sth->fetch();
  430. }
  431. public function fetchAll($sql, $values = []) {
  432. $sth = $this->prepare($sql);
  433. if (!empty($values)) {
  434. $this->bindValues($sth, $values);
  435. DBG::log($this->getRawSql($sth), 'sql');
  436. } else {
  437. DBG::log($sql, 'sql');
  438. }
  439. $sth->execute();
  440. return $sth->fetchAll();
  441. }
  442. public function fetchAllByKey($sql, $key = 'ID', $values = []) {
  443. $rowsByKey = array();
  444. $sth = $this->prepare($sql);
  445. if (!empty($values)) {
  446. $this->bindValues($sth, $values);
  447. DBG::log($this->getRawSql($sth), 'sql');
  448. } else {
  449. DBG::log($sql, 'sql');
  450. }
  451. $sth->execute();
  452. $rows = $sth->fetchAll();
  453. foreach ($rows as $row) {
  454. $keyRow = V::get($key, null, $row);
  455. $rowsByKey[$keyRow] = $row;
  456. }
  457. return $rowsByKey;
  458. }
  459. public function bindValues($sth, $values) {
  460. foreach ($values as $name => $value) {
  461. $val = $value;
  462. $type = PDO::PARAM_STR;
  463. if (is_array($value)) {
  464. $val = $value[0];
  465. if (count($value) > 1) {
  466. $type = $value[1];
  467. }
  468. }
  469. $sth->bindValue($name, $val, $type);
  470. if (!isset($sth->bindedValues)) $sth->bindedValues = array();
  471. $sth->bindedValues[$name] = array($val, $type);
  472. }
  473. }
  474. public function getRawSql($sth, $values = array()) {
  475. $sql = $sth->queryString;
  476. $params = array();
  477. if (!empty($sth->bindedValues)) {
  478. foreach ($sth->bindedValues as $name => $value) {
  479. $params[$name] = array($value[0], $value[1]);
  480. }
  481. }
  482. foreach ($values as $name => $value) {
  483. $val = $value;
  484. $type = PDO::PARAM_STR;
  485. if (is_array($value)) {
  486. $val = $value[0];
  487. if (count($value) > 1) {
  488. $type = $value[1];
  489. }
  490. }
  491. $params[$name] = array($val, $type);
  492. }
  493. if (!empty($params)) {
  494. foreach ($params as $name => $val) {
  495. $outValue = $val[0];
  496. if (PDO::PARAM_STR == $val[1]) $outValue = "'{$outValue}'";
  497. $sql = str_replace((':' === $name[0] ? $name : ":{$name}"), $outValue, $sql);
  498. }
  499. }
  500. return $sql;
  501. }
  502. public function insert($tableName, $item, $sqlSchema = []) {// @returns int last inserted id
  503. if (empty($tableName)) throw new Exception("Missing table name");
  504. if (!is_array($item)) throw new Exception("Missing item");
  505. $sqlFields = [];
  506. $sqlValues = [];
  507. foreach ($item as $field => $val) {
  508. $sqlFields[] = "`{$field}`";
  509. $sqlValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
  510. }
  511. $sql = "
  512. insert into `{$tableName}` (" . implode(", ", $sqlFields) . ")
  513. values (" . implode(", ", $sqlValues) . ")
  514. ";
  515. $this->execSql($sql);
  516. return $this->lastInsertId();
  517. }
  518. public function update($tableName, $primaryKeyName, $primaryKey, $item, $sqlSchema = []) {// @returns int affected rows
  519. if (empty($tableName)) throw new Exception("Missing table name");
  520. if (empty($primaryKeyName)) throw new Exception("Missing primaryKey name");
  521. if (empty($primaryKey)) throw new Exception("Missing primaryKey");
  522. if (empty($item) || !is_array($item)) throw new Exception("Missing item");
  523. $sqlPrimaryKey = $this->quote($primaryKey, PDO::PARAM_STR);
  524. $sqlUpdateSet = [];
  525. foreach ($item as $field => $val) {
  526. $sqlUpdateSet[] = "`{$field}` = " . $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
  527. }
  528. $sql = "
  529. update `{$tableName}`
  530. set " . implode("\n , ", $sqlUpdateSet) . "
  531. where `{$primaryKeyName}` = {$sqlPrimaryKey}
  532. ";
  533. return $this->execSql($sql);
  534. }
  535. // '@insert' => [
  536. // 'A_RECORD_CREATE_AUTHOR' => User::getLogin(),
  537. // 'A_RECORD_CREATE_DATE' => 'NOW()',
  538. // ],
  539. // '@update' => [
  540. // 'A_RECORD_UPDATE_AUTHOR' => User::getLogin(),
  541. // 'A_RECORD_UPDATE_DATE' => 'NOW()',
  542. // ]
  543. public function insertOrUpdate($tableName, $item, $sqlSchema = []) {
  544. if (empty($tableName)) throw new Exception("Missing table name");
  545. if (empty($item) || !is_array($item)) throw new Exception("Missing item");
  546. $sqlFields = [];
  547. $sqlValues = [];
  548. $sqlUpdateSet = [];
  549. foreach ($item as $field => $val) {
  550. if ('@insert' == $field) continue;
  551. if ('@update' == $field) continue;
  552. $sqlVal = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
  553. $sqlFields[] = "`{$field}`";
  554. $sqlValues[] = $sqlVal;
  555. $sqlUpdateSet[] = "`{$field}` = {$sqlVal}";
  556. }
  557. if (!empty($item['@insert'])) {
  558. foreach ($item['@insert'] as $field => $val) {
  559. $sqlFields[] = "`{$field}`";
  560. $sqlValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
  561. }
  562. }
  563. if (!empty($item['@update'])) {
  564. foreach ($item['@update'] as $field => $val) {
  565. $sqlUpdateSet[] = "`{$field}` = " . $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
  566. }
  567. }
  568. $sql = "
  569. insert into `{$tableName}` (" . implode(", ", $sqlFields) . ")
  570. values (" . implode(", ", $sqlValues) . ")
  571. ";
  572. if (!empty($sqlUpdateSet)) $sql .= " on duplicate key update " . implode(", ", $sqlUpdateSet);
  573. $affected = $this->execSql($sql);
  574. return true; // return $affected; // $this->lastInsertId();
  575. }
  576. public function convertValueToSqlSafe($value, $xsdType = null) {
  577. if ('NOW()' === $value) return 'NOW()';
  578. else return $this->quote($value, PDO::PARAM_STR);// TODO: use $sqlSchema if set
  579. }
  580. public function execSql($sql, $values = []) {
  581. if (empty($values)) {
  582. DBG::log($sql, 'sql');
  583. return $this->exec($sql);
  584. }
  585. $sth = $this->prepare($sql);
  586. if (!empty($values)) {
  587. $this->bindValues($sth, $values);
  588. DBG::log($this->getRawSql($sth), 'sql');
  589. } else {
  590. DBG::log($sql, 'sql');
  591. }
  592. $sth->execute();
  593. return $sth->rowCount();
  594. }
  595. public function getBlob($tableName, $fieldName, $pkField, $primaryKey) {
  596. if (empty($tableName)) throw new Exception("Missing tableName in PDO::getBlob");
  597. if (empty($fieldName)) throw new Exception("Missing fieldName in PDO::getBlob");
  598. if (empty($pkField)) throw new Exception("Missing pkField in PDO::getBlob");
  599. if (empty($primaryKey)) throw new Exception("Missing primaryKey in PDO::getBlob");
  600. $dbType = $this->getType();
  601. switch ($dbType) {
  602. case 'mysql': {
  603. $sql = "
  604. select `{$fieldName}`
  605. from `{$tableName}`
  606. where `{$pkField}` = :pk
  607. limit 1
  608. ";
  609. $sth = $this->prepare($sql);
  610. $sth->bindValue(':pk', $primaryKey, PDO::PARAM_STR);
  611. $sth->execute();
  612. $sth->bindColumn(1, $content, PDO::PARAM_LOB);
  613. $sth->fetch();
  614. return $content;
  615. } break;
  616. default: throw new Exception("Not implemented getBlob for database type '{$dbType}'");
  617. }
  618. }
  619. }