platform/classes/Db/Mysql.php - Q Platform
Show:

File: platform/classes/Db/Mysql.php

  1. <?php
  2.  
  3. /**
  4. * @module Db
  5. */
  6.  
  7. class Db_Mysql implements Db_Interface
  8. {
  9. /**
  10. * This class lets you create and use PDO database connections.
  11. * @class Db_Mysql
  12. * @extends Db_Interface
  13. * @constructor
  14. *
  15. * @param {string} $connectionName The name of the connection out of the connections added with Db::setConnection()
  16. * This is required for actually connecting to the database.
  17. * @param {PDO} [$pdo=null] Existing PDO connection. Only accepts connections to MySQL.
  18. */
  19. function __construct ($connectionName, PDO $pdo = null)
  20. {
  21. $this->connectionName = $connectionName;
  22. if ($pdo) {
  23. // The following statement may throw an exception, which is fine.
  24. $driver_name = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
  25. if (strtolower($driver_name) != 'mysql')
  26. throw new Exception("the PDO object is not for mysql", -1);
  27.  
  28. $this->pdo = $pdo;
  29. }
  30. }
  31.  
  32. /**
  33. * The PDO connection that this object uses
  34. * @property $pdo
  35. * @type PDO
  36. */
  37. public $pdo;
  38. /**
  39. * The name of the connection
  40. * @property $connectionName
  41. * @type string
  42. * @protected
  43. */
  44. protected $connectionName;
  45. /**
  46. * The name of the shard currently selected with reallyConnect, if any
  47. * @property $shardName
  48. * @type string
  49. * @protected
  50. */
  51. protected $shardName;
  52.  
  53. /**
  54. * The database name of the shard currently selected with reallyConnect, if any
  55. * @property $dbname
  56. * @type string
  57. */
  58. public $dbname;
  59.  
  60. /**
  61. * The prefix of the shard currently selected with reallyConnect, if any
  62. * @property $prefix
  63. * @type string
  64. */
  65. public $prefix;
  66.  
  67. /**
  68. * The cutoff after which strlen gets too expensive to check automatically
  69. * @property $maxCheckStrlen
  70. * @type string
  71. */
  72. public $maxCheckStrlen = 1000000;
  73.  
  74. /**
  75. * Actually makes a connection to the database (by creating a PDO instance)
  76. * @method reallyConnect
  77. * @param {array} [$shardName=null] A shard name that was added using Db::setShard.
  78. * This modifies how we connect to the database.
  79. * @return {PDO} The PDO object for connection
  80. */
  81. function reallyConnect($shardName = null)
  82. {
  83. if ($this->pdo) {
  84. return $this->pdo;
  85. }
  86. $connectionName = $this->connectionName;
  87. $connectionInfo = Db::getConnection($connectionName);
  88. if (empty($connectionInfo)) {
  89. throw new Exception("database connection \"$connectionName\" wasn't registered with Db.", -1);
  90. }
  91. if (empty($shardName)) {
  92. $shardName = '';
  93. }
  94. $modifications = Db::getShard($connectionName, $shardName);
  95. if (!isset($modifications)) {
  96. $modifications = array();
  97. }
  98. if (class_exists('Q')) {
  99. /**
  100. * Occurs before a real connection to the database is made
  101. * @event Db/reallyConnect {before}
  102. * @param {Db_Mysql} db
  103. * @param {string} shardName
  104. * @param {array} modifications
  105. * @return {array}
  106. * Extra modifications
  107. */
  108. $more = Q::event('Db/reallyConnect', array(
  109. 'db' => $this,
  110. 'shardName' => $shardName,
  111. 'modifications' => $modifications
  112. ), 'before');
  113. if ($more) {
  114. $modifications = array_merge($modifications, $more);
  115. }
  116. }
  117. $dsn = isset($modifications['dsn']) ? $modifications['dsn'] : $connectionInfo['dsn'];
  118. $prefix = isset($modifications['prefix']) ? $modifications['prefix'] : $connectionInfo['prefix'];
  119. $username = isset($modifications['username']) ? $modifications['username'] : $connectionInfo['username'];
  120. $password = isset($modifications['password']) ? $modifications['password'] : $connectionInfo['password'];
  121. $driver_options = isset($modifications['driver_options'])
  122. ? $modifications['driver_options']
  123. : isset($connectionInfo['driver_options']) ? $connectionInfo['driver_options'] : null;
  124.  
  125. // More dsn changes
  126. $dsn_fields = array();
  127. foreach (array('host', 'port', 'dbname', 'unix_socket', 'charset') as $f) {
  128. if (isset($modifications[$f])) {
  129. $dsn_fields[$f] = $modifications[$f];
  130. }
  131. }
  132. if ($dsn_fields) {
  133. $dsn_array = array_merge(Db::parseDsnString($dsn), $dsn_fields);
  134. $dsn = 'mysql:'.http_build_query($dsn_array, '', ';');
  135. } else {
  136. $dsn_array = Db::parseDsnString($dsn);
  137. }
  138.  
  139. // The connection may have already been made with these parameters,
  140. // in which case we will just retrieve the existing connection.
  141. $this->pdo = Db::pdo($dsn, $username, $password, $driver_options);
  142. $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  143. $this->shardName = $shardName;
  144. $this->dbname = $dsn_array['dbname'];
  145. $this->prefix = $prefix;
  146.  
  147. if (class_exists('Q')) {
  148. /**
  149. * Occurs when a real connection to the database has been made
  150. * @event Db/reallyConnect {after}
  151. * @param {Db_Mysql} db
  152. * @param {string} shardName
  153. * @param {array} modifications
  154. */
  155. Q::event('Db/reallyConnect', array(
  156. 'db' => $this,
  157. 'shardName' => $shardName,
  158. 'modifications' => $modifications
  159. ), 'after');
  160. }
  161. $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
  162. $this->pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
  163. $this->setTimezone();
  164. return $this->pdo;
  165. }
  166. /**
  167. * Sets the timezone in the database to match the one in PHP
  168. * @param {integer} [$offset=timezone_offset_get()] in seconds
  169. * @method setTimezone
  170. */
  171. function setTimezone($offset = null)
  172. {
  173. if (!isset($offset)) {
  174. $offset = (int)date('Z');
  175. }
  176. if (!$offset) {
  177. $offset = 0;
  178. }
  179. $abs = abs($offset);
  180. $hours = sprintf("%02d", floor($abs / 3600));
  181. $minutes = sprintf("%02d", floor(($abs % 3600) / 60));
  182. $sign = ($offset > 0) ? '+' : '-';
  183. $this->rawQuery("SET time_zone = '$sign$hours:$minutes';")->execute();
  184. }
  185. /**
  186. * Returns the name of the shard currently selected with reallyConnect, if any
  187. * @method shardName
  188. * @return {string}
  189. */
  190. function shardName()
  191. {
  192. return $this->shardName;
  193. }
  194.  
  195. /**
  196. * Forwards all other calls to the PDO object
  197. * @method __call
  198. * @param {string} $name The function name
  199. * @param {array} $arguments The arguments
  200. * @return {mixed} The result of method call
  201. */
  202. function __call ($name, array $arguments)
  203. {
  204. $this->reallyConnect();
  205. if (!is_callable(array($this->pdo, $name))) {
  206. throw new Exception("neither Db_Mysql nor PDO supports the $name function");
  207. }
  208. return call_user_func_array(array($this->pdo, $name), $arguments);
  209. }
  210.  
  211. /**
  212. * Returns the name of the connection with which this Db object was created.
  213. * @method connectionName
  214. * @return {string}
  215. */
  216. function connectionName ()
  217. {
  218. return isset($this->connectionName) ? $this->connectionName : null;
  219. }
  220. /**
  221. * Returns the connection info with which this Db object was created.
  222. * @method connection
  223. * @return {string}
  224. */
  225. function connection()
  226. {
  227. if (isset($this->connectionName)) {
  228. return Db::getConnection($this->connectionName);
  229. }
  230. return null;
  231. }
  232. /**
  233. * Returns an associative array representing the dsn
  234. * @method dsn
  235. * @return {array}
  236. */
  237. function dsn()
  238. {
  239. $connectionInfo = Db::getConnection($this->connectionName);
  240. if (empty($connectionInfo['dsn'])) {
  241. throw new Exception(
  242. 'No dsn string found for the connection '
  243. . $this->connectionName
  244. );
  245. }
  246. return Db::parseDsnString($connectionInfo['dsn']);
  247. }
  248. /**
  249. * Returns the lowercase name of the dbms (e.g. "mysql")
  250. * @method dbms
  251. * @return {string}
  252. */
  253. function dbms()
  254. {
  255. return 'mysql';
  256. }
  257. /**
  258. * Returns the name of the database used
  259. * @method dbName
  260. * @return {string}
  261. */
  262. function dbName()
  263. {
  264. $dsn = $this->dsn();
  265. if (empty($dsn))
  266. return null;
  267. return $dsn['dbname'];
  268. }
  269.  
  270. /**
  271. * Creates a query to select fields from a table. Needs to be used with Db_Query::from().
  272. * @method select
  273. * @param {string|array} [$fields='*'] The fields as strings, or "*", or array of alias=>field
  274. * @param {string|array} [$tables=''] The tables as strings, or array of alias=>table
  275. * @return {Db_Query_Mysql} The resulting Db_Query object
  276. */
  277. function select ($fields = '*', $tables = '')
  278. {
  279. if (empty($fields))
  280. throw new Exception("fields not specified in call to 'select'.");
  281. if (!isset($tables))
  282. throw new Exception("tables not specified in call to 'select'.");
  283. $query = new Db_Query_Mysql($this, Db_Query::TYPE_SELECT);
  284. return $query->select($fields, $tables);
  285. }
  286.  
  287. /**
  288. * Creates a query to insert a row into a table
  289. * @method insert
  290. * @param {string} $table_into The name of the table to insert into
  291. * @param {array} $fields=array() The fields as an array of column=>value pairs
  292. * @return {Db_Query_Mysql} The resulting Db_Query_Mysql object
  293. */
  294. function insert ($table_into, array $fields = array())
  295. {
  296. if (empty($table_into))
  297. throw new Exception("table not specified in call to 'insert'.");
  298. // $fields might be an empty array,
  299. // but the insert will still be attempted.
  300. $columns_list = array();
  301. $values_list = array();
  302. foreach ($fields as $column => $value) {
  303. $columns_list[] = Db_Query_Mysql::column($column);
  304. if ($value instanceof Db_Expression) {
  305. $values_list[] = "$value";
  306. } else {
  307. $values_list[] = ":$column";
  308. }
  309. }
  310. $columns_string = implode(', ', $columns_list);
  311. $values_string = implode(', ', $values_list);
  312. $clauses = array(
  313. 'INTO' => "$table_into ($columns_string)", 'VALUES' => $values_string
  314. );
  315. return new Db_Query_Mysql($this, Db_Query::TYPE_INSERT, $clauses, $fields, $table_into);
  316. }
  317.  
  318. /**
  319. * Inserts multiple rows into a single table, preparing the statement only once,
  320. * and executes all the queries.
  321. * @method insertManyAndExecute
  322. * @param {string} $table_into The name of the table to insert into
  323. * @param {array} [$rows=array()] The array of rows to insert.
  324. * Each row should be an array of ($field => $value) pairs, with the exact
  325. * same set of keys (field names) in each array. It can also be a Db_Row.
  326. * @param {array} [$options=array()] An associative array of options, including:
  327. * @param {string} [$options.className]
  328. * If you provide the class name, the system will be able to use any sharding
  329. * indexes under that class name in the config.
  330. * @param {integer} [$options.chunkSize]
  331. * The number of rows to insert at a time. Defaults to 20.
  332. * You can also put 0 here, which means unlimited chunks, but it's not recommended.
  333. * @param {array} [$options.onDuplicateKeyUpdate]
  334. * You can put an array of fieldname => value pairs here,
  335. * which will add an ON DUPLICATE KEY UPDATE clause to the query.
  336. */
  337. function insertManyAndExecute ($table_into, array $rows = array(), $options = array())
  338. {
  339. // Validate and get options
  340. if (empty($table_into)) {
  341. throw new Exception("table not specified in call to 'insertManyAndExecute'.");
  342. }
  343. if (empty($rows)) {
  344. return false;
  345. }
  346. $chunkSize = isset($options['chunkSize']) ? $options['chunkSize'] : 20;
  347. if ($chunkSize < 0) {
  348. return false;
  349. }
  350. $onDuplicateKeyUpdate = isset($options['onDuplicateKeyUpdate'])
  351. ? $options['onDuplicateKeyUpdate'] : null;
  352. $className = isset($options['className']) ? $options['className'] : null;
  353. // Get the columns list
  354. $row = reset($rows);
  355. $record = ($row instanceof Db_Row) ? $row->fields : $row;
  356. foreach ($record as $column => $value) {
  357. $columns_list[] = Db_Query_Mysql::column($column);
  358. }
  359. $columns_string = implode(', ', $columns_list);
  360. $into = "$table_into ($columns_string)";
  361. // On duplicate key update clause (optional)
  362. $update_fields = array();
  363. $odku_clause = '';
  364. if (isset($onDuplicateKeyUpdate)) {
  365. $odku_clause = "\n\t ON DUPLICATE KEY UPDATE ";
  366. $parts = array();
  367. foreach ($onDuplicateKeyUpdate as $k => $v) {
  368. if ($v instanceof Db_Expression) {
  369. $part = "= $v";
  370. } else {
  371. $part = " = :__update_$k";
  372. $update_fields["__update_$k"] = $v;
  373. }
  374. $parts[] .= Db_Query_Mysql::column($k) . $part;
  375. }
  376. $odku_clause .= implode(",\n\t", $parts);
  377. }
  378. // Start filling
  379. $queries = array();
  380. $queryCounts = array();
  381. $bindings = array();
  382. $last_q = array();
  383. $last_queries = array();
  384. foreach ($rows as $row) {
  385. // get shard, if any
  386. $record = ($row instanceof Db_Row) ? $row->fields : $row;
  387. $query = new Db_Query_Mysql($this, Db_Query::TYPE_INSERT);
  388. $shard = '';
  389. if (isset($className)) {
  390. $query->className = $className;
  391. $sharded = $query->shard(null, $record);
  392. if (count($sharded) > 1 or $shard === '*') { // should be only one shard
  393. throw new Exception("Db_Mysql::insertManyAndExecute row should be stored on exactly one shard: " . Q::json_encode($record));
  394. }
  395. $shard = key($sharded);
  396. }
  397. // start filling out the query data
  398. $qc = empty($queryCounts[$shard]) ? 1 : $queryCounts[$shard] + 1;
  399. if (!isset($bindings[$shard])) {
  400. $bindings[$shard] = array();
  401. }
  402. $values_list = array();
  403. foreach ($record as $column => $value) {
  404. if ($value instanceof Db_Expression) {
  405. $values_list[] = "$value";
  406. } else {
  407. $values_list[] = ':_'.$qc.'_'.$column;
  408. $bindings[$shard]['_'.$qc.'_'.$column] = $value;
  409. }
  410. }
  411. $values_string = implode(', ', $values_list);
  412. if (empty($queryCounts[$shard])) {
  413. $q = $queries[$shard] = "INSERT INTO $into\nVALUES ($values_string) ";
  414. $queryCounts[$shard] = 1;
  415. } else {
  416. $q = $queries[$shard] .= ",\n ($values_string) ";
  417. ++$queryCounts[$shard];
  418. }
  419.  
  420. // if chunk filled up for this shard, execute it
  421. if ($qc === $chunkSize) {
  422. if ($onDuplicateKeyUpdate) {
  423. $q .= $odku_clause;
  424. }
  425. $query = $this->rawQuery($q)->bind($bindings[$shard]);
  426. if ($onDuplicateKeyUpdate) {
  427. $query = $query->bind($update_fields);
  428. }
  429. if (isset($last_q[$shard]) and $last_q[$shard] === $q) {
  430. // re-use the prepared statement, save round-trips to the db
  431. $query->reuseStatement($last_queries[$shard]);
  432. }
  433. $query->execute(true, $shard);
  434. $last_q[$shard] = $q;
  435. $last_queries[$shard] = $query; // save for re-use
  436. $bindings[$shard] = $queries[$shard] = array();
  437. $queryCounts[$shard] = 0;
  438. }
  439. }
  440. // Now execute the remaining queries, if any
  441. foreach ($queries as $shard => $q) {
  442. if (!$q) continue;
  443. if ($onDuplicateKeyUpdate) {
  444. $q .= $odku_clause;
  445. }
  446. $query = $this->rawQuery($q)->bind($bindings[$shard]);
  447. if ($onDuplicateKeyUpdate) {
  448. $query = $query->bind($update_fields);
  449. }
  450. if (isset($last_q[$shard]) and $last_q[$shard] === $q) {
  451. // re-use the prepared statement, save round-trips to the db
  452. $query->reuseStatement($last_queries[$shard]);
  453. }
  454. $query->execute(true, $shard);
  455. }
  456. foreach ($rows as $row) {
  457. if ($row instanceof Db_Row) {
  458. $row->wasInserted(true);
  459. $row->wasRetrieved(true);
  460. }
  461. }
  462. }
  463.  
  464. /**
  465. * Creates a query to update rows. Needs to be used with {@link Db_Query::set}
  466. * @method update
  467. * @param {string} $table The table to update
  468. * @return {Db_Query_Mysql} The resulting Db_Query object
  469. */
  470. function update ($table)
  471. {
  472. if (empty($table))
  473. throw new Exception("table not specified in call to 'update'.");
  474. $clauses = array('UPDATE' => "$table");
  475. return new Db_Query_Mysql($this, Db_Query::TYPE_UPDATE, $clauses, array(), $table);
  476. }
  477.  
  478. /**
  479. * Creates a query to delete rows.
  480. * @method delete
  481. * @param {string} $table_from The table to delete from
  482. * @param {string} [$table_using=null] If set, adds a USING clause with this table. You can then use ->join() with the resulting Db_Query.
  483. * @return {Db_Query_Mysql}
  484. */
  485. function delete ($table_from, $table_using = null)
  486. {
  487. if (empty($table_from))
  488. throw new Exception("table not specified in call to 'delete'.");
  489.  
  490. if (isset($table_using) and !is_string($table_using)) {
  491. throw new Exception("table_using field must be a string");
  492. }
  493.  
  494. if (isset($table_using))
  495. $clauses = array('FROM' => "$table_from USING $table_using");
  496. else
  497. $clauses = array('FROM' => "$table_from");
  498. return new Db_Query_Mysql($this, Db_Query::TYPE_DELETE, $clauses, array(), $table_from);
  499. }
  500.  
  501. /**
  502. * Creates a query from raw SQL
  503. * @method rawQuery
  504. * @param {string|null} $sql May contain one or more SQL statements.
  505. * Pass null here for an empty query that you can add other clauses to, e.g. ->commit().
  506. * @param {array} [$bind=array()] An array of parameters to bind to the query, using
  507. * the Db_Query_Mysql->bind method.
  508. * @return {Db_Query_Mysql}
  509. */
  510. function rawQuery ($sql = null, $bind = array())
  511. {
  512. $clauses = array('RAW' => $sql);
  513. $query = new Db_Query_Mysql($this, Db_Query::TYPE_RAW, $clauses);
  514. if ($bind) {
  515. $query->bind($bind);
  516. }
  517. return $query;
  518. }
  519. /**
  520. * Creates a query to rollback a previously started transaction.
  521. * @method update
  522. * @param {array} $criteria The criteria to use, for sharding
  523. * @return {Db_Query_Mysql} The resulting Db_Query object
  524. */
  525. function rollback ($criteria = null)
  526. {
  527. $query = new Db_Query_Mysql($this, Db_Query::TYPE_ROLLBACK, array('ROLLBACK' => true));
  528. $query->rollback($criteria);
  529. return $query;
  530. }
  531.  
  532. /**
  533. * Sorts a table in chunks
  534. * @method rank
  535. * @param {string} $table The name of the table in the database
  536. * @param {string} $pts_field The name of the field to rank by.
  537. * @param {string} $rank_field The rank field to update in all the rows
  538. * @param {integer} [$start=1] The value of the first rank
  539. * @param {integer} [$chunk_size=1000] The number of rows to process at a time. Default is 1000.
  540. * This is so the queries don't tie up the database server for very long,
  541. * letting it service website requests and other things.
  542. * @param {integer} [$rank_level2=0] Since the ranking is done in chunks, the function must know
  543. * which rows have not been processed yet. If this field is empty (default)
  544. * then the function sets the rank_field to 0 in all the rows, before
  545. * starting the ranking process.
  546. * (That might be a time consuming operation.)
  547. * Otherwise, if $rank is a nonzero integer, then the function alternates
  548. * between the ranges
  549. * $start to $rank_level2, and $rank_level2 + $start to $rank_level2 * 2.
  550. * That is, after it is finished, all the ratings will be in one of these
  551. * two ranges.
  552. * If not empty, this should be a very large number, like a billion.
  553. * @param {array} [$order_by] The order clause to use when calculating ranks.
  554. * Default is array($pts_field, false)
  555. * @param {array} [$where=null] Any additional criteria to filter the table by.
  556. * The ranking algorithm will do its work within the results that match this criteria.
  557. * If your table is sharded, then all the work must be done within one shard.
  558. */
  559. function rank(
  560. $table,
  561. $pts_field,
  562. $rank_field,
  563. $start = 1,
  564. $chunk_size = 1000,
  565. $rank_level2 = 0,
  566. $order_by = null,
  567. $where = array())
  568. {
  569. if (!isset($order_by)) {
  570. $order_by = array($pts_field, false);
  571. }
  572. if (!isset($where)) {
  573. $where = '1';
  574. }
  575. // Count all the rows
  576. $query = $this->select('COUNT(1) _count', $table)->where($where);
  577. $sharded = $query->shard();
  578. $shard = key($sharded);
  579. if (count($sharded) > 1 or $shard === '*') { // should be only one shard
  580. throw new Exception("Db_Mysql::rank can work within at most one shard");
  581. }
  582. $row = $query->execute()->fetch(PDO::FETCH_ASSOC);
  583. $count = $row['_count'];
  584. if (empty($rank_level2)) {
  585. $this->update($table)
  586. ->set(array($rank_field => 0))
  587. ->where($where)
  588. ->execute();
  589. $rank_base = 0;
  590. $condition = "$rank_field = 0 OR $rank_field IS NULL";
  591. } else {
  592. $rows = $this->select($pts_field, $table)
  593. ->where("$rank_field < $rank_level2")
  594. ->andWhere($where)
  595. ->limit(1)
  596. ->fetchAll();
  597. if (!empty($rows)) {
  598. // There are no ranks above $rank_level2. Create ranks on level 2.
  599. $rank_base = $rank_level2;
  600. $condition = "$rank_field < $rank_level2";
  601. } else {
  602. // The ranks are all above $rank_level2. Create ranks on level 1.
  603. $rank_base = 0;
  604. $condition = "$rank_field >= $rank_level2";
  605. }
  606. }
  607. // Here comes the magic:
  608. $offset = 0;
  609. $rank_base += $start;
  610. $this->rawQuery("set @rank = $offset - 1")->execute(false, $shard);
  611. do {
  612. $query = $this->update($table)->set(array(
  613. $rank_field => new Db_Expression("$rank_base + (@rank := @rank + 1)")
  614. ))->where($condition);
  615. if ($where) {
  616. $query = $query->andWhere($where);
  617. }
  618. if ($order_by) {
  619. $query = call_user_func_array(array($query, 'orderBy'), $order_by);
  620. }
  621. $query->limit($chunk_size)->execute();
  622. $offset += $chunk_size;
  623. } while ($count-$offset > 0);
  624. }
  625.  
  626. /**
  627. * Generate an ID that is unique in a table
  628. * @method uniqueId
  629. * @param {string} $table The name of the table
  630. * @param {string} $field The name of the field to check for uniqueness.
  631. * You should probably have an index starting with this field.
  632. * @param {array} [$where=array()] You can indicate conditions here to limit the search for
  633. * an existing value. The result is an id that is unique within a certain partition.
  634. * @param {array} [$options=array()] Optional array used to override default options:
  635. * @param {integer} [$options.length=8] The length of the ID to generate, after the prefix.
  636. * @param {string} [$options.characters='abcdefghijklmnopqrstuvwxyz'] All the characters from which to construct the id
  637. * @param {string} [$options.prefix=''] The prefix to prepend to the unique id.
  638. * @param {callable} [$options.filter]
  639. * The name of a function that will take the generated string and
  640. * check it. The filter function can modify the string by returning another string,
  641. * or simply reject the string by returning false, in which another string will be
  642. */
  643. function uniqueId(
  644. $table,
  645. $field,
  646. $where = array(),
  647. $options = array())
  648. {
  649. $length = 8;
  650. $characters = 'abcdefghijklmnopqrstuvwxyz';
  651. $prefix = '';
  652. extract($options);
  653. $count = strlen($characters);
  654. do {
  655. $id = $prefix;
  656. for ($i=0; $i<$length; ++$i) {
  657. $id .= $characters[mt_rand(0, $count-1)];
  658. }
  659. if (!empty($options['filter'])) {
  660. $ret = Q::call($options['filter'], array(compact('id', 'table', 'field', 'where', 'options')));
  661. if ($ret === false) {
  662. continue;
  663. } else if ($ret) {
  664. $id = $ret;
  665. }
  666. }
  667. $q = $this->select($field, $table)
  668. ->where(array($field => $id));
  669. if ($where) {
  670. $q->andWhere($where);
  671. }
  672. $rows = $q->limit(1)->fetchAll();
  673. } while ($rows);
  674. return $id;
  675. }
  676.  
  677. /**
  678. * Returns a timestamp from a Date string
  679. * @method fromDate
  680. * @param {string} $datetime The Date string that comes from the db
  681. * @return {integer} The timestamp
  682. */
  683. function fromDate ($date)
  684. {
  685. $year = substr($date, 0, 4);
  686. $month = substr($date, 5, 2);
  687. $day = substr($date, 8, 2);
  688.  
  689. return mktime(0, 0, 0, $month, $day, $year);
  690. }
  691. /**
  692. * Returns a timestamp from a DateTime string
  693. * @method fromDateTime
  694. * @param {string} $datetime The DateTime string that comes from the db
  695. * @return {integer} The timestamp
  696. */
  697. function fromDateTime ($datetime)
  698. {
  699. if (is_numeric($datetime)) {
  700. return $datetime;
  701. }
  702. $year = substr($datetime, 0, 4);
  703. $month = substr($datetime, 5, 2);
  704. $day = substr($datetime, 8, 2);
  705. $hour = substr($datetime, 11, 2);
  706. $min = substr($datetime, 14, 2);
  707. $sec = substr($datetime, 17, 2);
  708.  
  709. return mktime($hour, $min, $sec, $month, $day, $year);
  710. }
  711.  
  712. /**
  713. * Returns a Date string to store in the database
  714. * @method toDate
  715. * @param {string} $timestamp The UNIX timestamp, e.g. from a strtotime function
  716. * @return {string}
  717. */
  718. function toDate ($timestamp)
  719. {
  720. if (!is_numeric($timestamp)) {
  721. $timestamp = strtotime($timestamp);
  722. }
  723. if ($timestamp > 10000000000) {
  724. $timestamp = $timestamp / 1000;
  725. }
  726. return date('Y-m-d', $timestamp);
  727. }
  728.  
  729. /**
  730. * Returns a DateTime string to store in the database
  731. * @method toDateTime
  732. * @param {string} $timestamp The UNIX timestamp, e.g. from a strtotime function
  733. * @return {string}
  734. */
  735. function toDateTime ($timestamp)
  736. {
  737. if (!is_numeric($timestamp)) {
  738. $timestamp = strtotime($timestamp);
  739. }
  740. if ($timestamp > 10000000000) {
  741. $timestamp = $timestamp / 1000;
  742. }
  743. return date('Y-m-d H:i:s', $timestamp);
  744. }
  745. /**
  746. * Returns the timestamp the db server would have, based on synchronization
  747. * @method timestamp
  748. * @return {integer}
  749. */
  750. function getCurrentTimestamp()
  751. {
  752. static $dbtime = null, $phptime = null;
  753. if (!isset($dbtime)) {
  754. $phptime1 = time();
  755. $row = $this->select('CURRENT_TIMESTAMP', '')->execute()->fetch(PDO::FETCH_NUM);
  756. $dbtime = $this->fromDateTime($row[0]);
  757. $phptime2 = time();
  758. $phptime = round(($phptime1 + $phptime2) / 2);
  759. }
  760. return $dbtime + (time() - $phptime);
  761. }
  762. /**
  763. * Takes a MySQL script and returns an array of queries.
  764. * When DELIMITER is changed, respects that too.
  765. * @method scriptToQueries
  766. * @param {string} $script The text of the script
  767. * @param {callable} [$callback=null] Optional callback to call for each query.
  768. * @return {array} An array of the SQL queries.
  769. */
  770. function scriptToQueries($script, $callback = null)
  771. {
  772. $this->reallyConnect();
  773. $version_string = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
  774. $version_parts = explode('.', $version_string);
  775. sprintf("%1d%02d%02d", $version_parts[0], $version_parts[1], $version_parts[2]);
  776. $script_stripped = $script;
  777. return $this->scriptToQueries_internal($script_stripped, $callback);
  778. }
  779. /**
  780. * Takes stripped MySQL script and returns an array of queries.
  781. * When DELIMITER is changed, respects that too.
  782. * @method scriptToQueries_internal
  783. * @protected
  784. * @param {string} $script The text of the script
  785. * @param {callable} [$callback=null] Optional callback to call for each query.
  786. * @return {array} An array of the SQL queries.
  787. */
  788. protected function scriptToQueries_internal($script, $callback = null)
  789. {
  790. $queries = array();
  791. $script_len = strlen($script);
  792. $this->reallyConnect();
  793. $version_string = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
  794. $version_parts = explode('.', $version_string);
  795. $version = sprintf("%1d%02d%02d", $version_parts[0], $version_parts[1], $version_parts[2]);
  796. //$mode_n = 0; // normal
  797. $mode_c = 1; // comments
  798. $mode_sq = 2; // single quotes
  799. $mode_dq = 3; // double quotes
  800. $mode_bt = 4; // backticks
  801. $mode_lc = 5; // line comment (hash or double-dash)
  802. $mode_ds = 6; // delimiter statement
  803. $cur_pos = 0;
  804. $d = ';'; // delimiter
  805. $d_len = strlen($d);
  806. $query_start_pos = 0;
  807. $del_start_pos_array = array();
  808. $del_end_pos_array = array();
  809. if (class_exists('Q_Config')) {
  810. $separator = Q_Config::expect('Db', 'sql', 'querySeparator');
  811. } else {
  812. $separator = "-------- NEXT QUERY STARTS HERE --------";
  813. }
  814. $found = strpos($script, $separator);
  815. if ($found !== false) {
  816. // This script was specially crafted for quick parsing
  817. $queries = explode($separator, $script);
  818. foreach ($queries as $i => $query) {
  819. if (!trim($query)) {
  820. unset($queries[$i]);
  821. }
  822. }
  823. return $queries;
  824. }
  825. while (1) {
  826. $c_pos = strpos($script, "/*", $cur_pos);
  827. $sq_pos = strpos($script, "'", $cur_pos);
  828. $dq_pos = strpos($script, "\"", $cur_pos);
  829. $bt_pos = strpos($script, "`", $cur_pos);
  830. $c2_pos = strpos($script, "--", $cur_pos);
  831. $c3_pos = strpos($script, "#", $cur_pos);
  832. $ds_pos = stripos($script, "\nDELIMITER ", $cur_pos);
  833. if ($cur_pos === 0 and substr($script, 0, 9) === 'DELIMITER') {
  834. $ds_pos = 0;
  835. }
  836.  
  837. $next_pos = false;
  838. if ($c_pos !== false) {
  839. $next_mode = $mode_c;
  840. $next_pos = $c_pos;
  841. $next_end_str = "*/";
  842. $next_end_str_len = 2;
  843. }
  844. if ($sq_pos !== false and ($next_pos === false or $sq_pos < $next_pos)) {
  845. $next_mode = $mode_sq;
  846. $next_pos = $sq_pos;
  847. $next_end_str = "'";
  848. $next_end_str_len = 1;
  849. }
  850. if ($dq_pos !== false and ($next_pos === false or $dq_pos < $next_pos)) {
  851. $next_mode = $mode_dq;
  852. $next_pos = $dq_pos;
  853. $next_end_str = "\"";
  854. $next_end_str_len = 1;
  855. }
  856. if ($bt_pos !== false and ($next_pos === false or $bt_pos < $next_pos)) {
  857. $next_mode = $mode_bt;
  858. $next_pos = $bt_pos;
  859. $next_end_str = "`";
  860. $next_end_str_len = 1;
  861. }
  862. if ($c2_pos !== false and ($next_pos === false or $c2_pos < $next_pos)
  863. and ($script[$c2_pos+2] == " " or $script[$c2_pos+2] == "\t")) {
  864. $next_mode = $mode_lc;
  865. $next_pos = $c2_pos;
  866. $next_end_str = "\n";
  867. $next_end_str_len = 1;
  868. }
  869. if ($c3_pos !== false and ($next_pos === false or $c3_pos < $next_pos)) {
  870. $next_mode = $mode_lc;
  871. $next_pos = $c3_pos;
  872. $next_end_str = "\n";
  873. $next_end_str_len = 1;
  874. }
  875. if ($ds_pos !== false and ($next_pos === false or $ds_pos < $next_pos)) {
  876. $next_mode = $mode_ds;
  877. $next_pos = $ds_pos;
  878. $next_end_str = "\n";
  879. $next_end_str_len = 1;
  880. }
  881. // If at this point, $next_pos === false, then
  882. // we are in the final stretch.
  883. // Until the end of the string, we have normal mode.
  884. // Right now, we are in normal mode.
  885. $d_pos = strpos($script, $d, $cur_pos);
  886. while ($d_pos !== false and ($next_pos === false or $d_pos < $next_pos)) {
  887. $query = substr($script, $query_start_pos, $d_pos - $query_start_pos);
  888. // remove parts of the query string based on the "del_" arrays
  889. $del_pos_count = count($del_start_pos_array);
  890. if ($del_pos_count == 0) {
  891. $query2 = $query;
  892. } else {
  893. $query2 = substr($query, 0, $del_start_pos_array[0] - $query_start_pos);
  894. for ($i=1; $i < $del_pos_count; ++$i) {
  895. $query2 .= substr($query, $del_end_pos_array[$i-1] - $query_start_pos,
  896. $del_start_pos_array[$i] - $del_end_pos_array[$i-1]);
  897. }
  898. $query2 .= substr($query,
  899. $del_end_pos_array[$del_pos_count - 1] - $query_start_pos);
  900. }
  901. $del_start_pos_array = array(); // reset these arrays
  902. $del_end_pos_array = array(); // reset these arrays
  903. $query_start_pos = $d_pos + $d_len;
  904. $cur_pos = $query_start_pos;
  905. $query2 = trim($query2);
  906. if ($query2)
  907. $queries[] = $query2; // <----- here is where we add to the main array
  908. if ($callback) {
  909. call_user_func($callback, $query2);
  910. }
  911. $d_pos = strpos($script, $d, $cur_pos);
  912. };
  913. if ($next_pos === false) {
  914. // Add the last query and get out of here:
  915. $query = substr($script, $query_start_pos);
  916.  
  917. // remove parts of the query string based on the "del_" arrays
  918. $del_pos_count = count($del_start_pos_array);
  919. if ($del_pos_count == 0) {
  920. $query2 = $query;
  921. } else {
  922. $query2 = substr($query, 0, $del_start_pos_array[0] - $query_start_pos);
  923. for ($i=1; $i < $del_pos_count; ++$i) {
  924. $query2 .= substr($query, $del_end_pos_array[$i-1] - $query_start_pos,
  925. $del_start_pos_array[$i] - $del_end_pos_array[$i-1]);
  926. }
  927. if ($del_end_pos_array[$del_pos_count - 1] !== false) {
  928. $query2 .= substr($query,
  929. $del_end_pos_array[$del_pos_count - 1] - $query_start_pos);
  930. }
  931. }
  932. $query2 = trim($query2);
  933. if ($query2) {
  934. $queries[] = $query2;
  935. if ($callback) {
  936. call_user_func($callback, $query2);
  937. }
  938. }
  939. break;
  940. }
  941. if ($next_mode == $mode_c) {
  942. // We are inside a comment
  943. $end_pos = strpos($script, $next_end_str, $next_pos + 1);
  944. if ($end_pos === false) {
  945. throw new Exception("unterminated comment -- missing terminating */ characters.");
  946. }
  947. $version_comment = false;
  948. if ($script[$next_pos + 2] == '!') {
  949. $ver = substr($script, $next_pos + 3, 5);
  950. if ($version >= $ver) {
  951. // we are in a version comment
  952. $version_comment = true;
  953. }
  954. }
  955. // Add to list of areas to ignore
  956. if ($version_comment) {
  957. $del_start_pos_array[] = $next_pos;
  958. $del_end_pos_array[] = $next_pos + 3 + 5;
  959. $del_start_pos_array[] = $end_pos;
  960. $del_end_pos_array[] = $end_pos + $next_end_str_len;
  961. } else {
  962. $del_start_pos_array[] = $next_pos;
  963. $del_end_pos_array[] = $end_pos + $next_end_str_len;
  964. }
  965. } else if ($next_mode == $mode_lc) {
  966. // We are inside a line comment
  967. $end_pos = strpos($script, $next_end_str, $next_pos + 1);
  968. $del_start_pos_array[] = $next_pos;
  969. if ($end_pos !== false) {
  970. $del_end_pos_array[] = $end_pos + $next_end_str_len;
  971. } else {
  972. $del_end_pos_array[] = false;
  973. }
  974. } else if ($next_mode == $mode_ds) {
  975. // We are inside a DELIMITER statement
  976. $start_pos = $next_pos;
  977. $end_pos = strpos($script, $next_end_str, $next_pos + 11);
  978. $del_start_pos_array[] = $next_pos;
  979. if ($end_pos !== false) {
  980. $del_end_pos_array[] = $end_pos + $next_end_str_len;
  981. } else {
  982. // this is the last statement in the script, it seems.
  983. // Might look funny, like:
  984. // DELIMITER aa sfghjkhsgkjlfhdsgjkfdglhdfsgkjfhgjdlk
  985. $del_end_pos_array[] = false;
  986. }
  987. // set a new delimiter!
  988. $try_d = trim(substr($script, $ds_pos + 11, $end_pos - ($ds_pos + 11)));
  989. if (!empty($try_d)) {
  990. $d = $try_d;
  991. $d_len = strlen($d);
  992. } // otherwise malformed delimiter statement or end of file
  993. } else {
  994. // We are inside a string
  995. $start_pos = $next_pos;
  996. $try_end_pos = $next_pos;
  997. do {
  998. $end_pos = false;
  999. $try_end_pos = strpos($script, $next_end_str, $try_end_pos + 1);
  1000. if ($try_end_pos === false) {
  1001. throw new Exception("unterminated string -- missing terminating $next_end_str character.");
  1002. }
  1003. if ($try_end_pos+1 >= $script_len) {
  1004. $end_pos = $try_end_pos;
  1005. break;
  1006. }
  1007. if ($script[$try_end_pos+1] == $next_end_str) {
  1008. ++$try_end_pos;
  1009. continue;
  1010. }
  1011. $bs_count = 0;
  1012. for ($i = $try_end_pos - 1; $i > $next_pos; --$i) {
  1013. if ($script[$i] == "\\") {
  1014. ++$bs_count;
  1015. } else {
  1016. break;
  1017. }
  1018. }
  1019. if ($bs_count % 2 == 0) {
  1020. $end_pos = $try_end_pos;
  1021. }
  1022. } while ($end_pos === false);
  1023. // If we are here, we have found the end of the string,
  1024. // and are back in normal mode.
  1025. }
  1026. // We have exited the previous mode and set end_pos.
  1027. if ($end_pos === false)
  1028. break;
  1029. $cur_pos = $end_pos + $next_end_str_len;
  1030. }
  1031. foreach ($queries as $i => $query) {
  1032. if ($query === false) {
  1033. unset($queries[$i]);
  1034. }
  1035. }
  1036.  
  1037. return $queries;
  1038. }
  1039. /**
  1040. * Generates base classes of the models, and if they don't exist,
  1041. * skeleton code for the models themselves.
  1042. * Use it only after you have made changes to the database schema.
  1043. * You shouldn't be using it on every request.
  1044. * @method generateModels
  1045. * @param {string} $directory The directory in which to generate the files.
  1046. * If the files already exist, they are not overwritten,
  1047. * unless they are inside the "Base" subdirectory.
  1048. * If the "Base" subdirectory does not exist, it is created.
  1049. * @param {string} [$classname_prefix=null] The prefix to prepend to the Base class names.
  1050. * If not specified, prefix becomes "connectionName_", where connectionName is the name of the connection.
  1051. * @return {array} $filenames The array of filenames for files that were saved.
  1052. * @throws {Exception} If the $connection is not registered, or the $directory
  1053. * does not exist, this function throws an exception.
  1054. */
  1055. function generateModels (
  1056. $directory,
  1057. $classname_prefix = null)
  1058. {
  1059. $dc = '/**';
  1060. if (!file_exists($directory))
  1061. throw new Exception("directory $directory does not exist.");
  1062. $connectionName = $this->connectionName();
  1063. $conn = Db::getConnection($connectionName);
  1064. $prefix = empty($conn['prefix']) ? '' : $conn['prefix'];
  1065. $prefix_len = strlen($prefix);
  1066. if (!isset($classname_prefix)) {
  1067. $classname_prefix = isset($connectionName) ? $connectionName . '_' : '';
  1068. }
  1069. $rows = $this->rawQuery('SHOW TABLES')->fetchAll();
  1070. if (class_exists('Q_Config')) {
  1071. $ext = Q_Config::get('Q', 'extensions', 'class', 'php');
  1072. } else {
  1073. $ext = 'php';
  1074. }
  1075. $table_classnames = array();
  1076. $js_table_classes_string = '';
  1077. $class_name_prefix = rtrim(ucfirst($classname_prefix), "._");
  1078. $filenames = array();
  1079.  
  1080. foreach ($rows as $row) {
  1081. $table_name = $row[0];
  1082. $table_name_base = substr($table_name, $prefix_len);
  1083. $table_name_prefix = substr($table_name, 0, $prefix_len);
  1084. if (empty($table_name_base) or $table_name_prefix != $prefix
  1085. or stristr($table_name, '_Q_') !== false) {
  1086. continue; // no class generated
  1087. }
  1088. $class_name_base = null;
  1089. $js_base_class_string = '';
  1090. $base_class_string = $this->codeForModelBaseClass(
  1091. $table_name,
  1092. $directory,
  1093. $classname_prefix,
  1094. $class_name_base,
  1095. null,
  1096. $js_base_class_string,
  1097. $table_comment
  1098. ); // sets the $class_name variable
  1099. $class_name = ucfirst($classname_prefix) . $class_name_base;
  1100. if (empty($class_name)) {
  1101. continue; // no class generated
  1102. }
  1103. $class_name_parts = explode('_', $class_name);
  1104. $class_filename = $directory.DS.implode(DS, $class_name_parts).'.php';
  1105. $base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.php';
  1106. $js_class_filename = $directory.DS.implode(DS, $class_name_parts).'.js';
  1107. $js_base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.js';
  1108. $js_base_class_require = 'Base/'.implode('/', $class_name_parts);
  1109. $js_class_name = implode('.', $class_name_parts);
  1110. $js_base_class_name = implode('.Base.', $class_name_parts);
  1111.  
  1112. $class_extras = is_readable($class_filename.'.inc') ? file_get_contents($class_filename.'.inc') : '';
  1113. $js_class_extras = is_readable($js_class_filename.'.inc') ? file_get_contents($js_class_filename.'.inc') : '';
  1114. if ($class_extras) {
  1115. $class_extras = <<<EOT
  1116. /* * * *
  1117. * Including content of '$class_name_base.php.inc' below
  1118. * * * */
  1119. $class_extras
  1120. /* * * */
  1121. EOT;
  1122. }
  1123. if ($js_class_extras) {
  1124. $js_class_extras = <<<EOT
  1125. /* * * *
  1126. * Including content of '$class_name_base.js.inc' below
  1127. * * * */
  1128. $class_extras
  1129. /* * * */
  1130. EOT;
  1131. }
  1132.  
  1133. $class_string = <<<EOT
  1134. <?php
  1135. $dc
  1136. * @module $connectionName
  1137. */
  1138. $dc
  1139. * Class representing '$class_name_base' rows in the '$connectionName' database
  1140. * You can create an object of this class either to
  1141. * access its non-static methods, or to actually
  1142. * represent a $table_name_base row in the $connectionName database.
  1143. *
  1144. * @class $class_name
  1145. * @extends Base_$class_name
  1146. */
  1147. class $class_name extends Base_$class_name
  1148. {
  1149. $dc
  1150. * The setUp() method is called the first time
  1151. * an object of this class is constructed.
  1152. * @method setUp
  1153. */
  1154. function setUp()
  1155. {
  1156. parent::setUp();
  1157. // INSERT YOUR CODE HERE
  1158. // e.g. \$this->hasMany(...) and stuff like that.
  1159. }
  1160.  
  1161. /*
  1162. * Add any $class_name methods here, whether public or not
  1163. */
  1164. $dc
  1165. * Implements the __set_state method, so it can work with
  1166. * with var_export and be re-imported successfully.
  1167. * @method __set_state
  1168. * @static
  1169. * @param {array} \$array
  1170. * @return {{$class_name}} Class instance
  1171. */
  1172. static function __set_state(array \$array) {
  1173. \$result = new $class_name();
  1174. foreach(\$array as \$k => \$v)
  1175. \$result->\$k = \$v;
  1176. return \$result;
  1177. }
  1178. };
  1179. EOT;
  1180.  
  1181. $js_class_string = <<<EOT
  1182. $dc
  1183. * Class representing $table_name_base rows.
  1184. *
  1185. * This description should be revised and expanded.
  1186. *
  1187. * @module $connectionName
  1188. */
  1189. var Q = require('Q');
  1190. var Db = Q.require('Db');
  1191. var $class_name_base = Q.require('$js_base_class_require');
  1192.  
  1193. $dc
  1194. * Class representing '$class_name_base' rows in the '$connectionName' database
  1195. $table_comment * @namespace $class_name_prefix
  1196. * @class $class_name_base
  1197. * @extends Base.$js_class_name
  1198. * @constructor
  1199. * @param {Object} fields The fields values to initialize table row as
  1200. * an associative array of {column: value} pairs
  1201. */
  1202. function $class_name (fields) {
  1203.  
  1204. // Run mixed-in constructors
  1205. $class_name.constructors.apply(this, arguments);
  1206. /*
  1207. * Add any privileged methods to the model class here.
  1208. * Public methods should probably be added further below.
  1209. */
  1210. }
  1211.  
  1212. Q.mixin($class_name, $class_name_base);
  1213.  
  1214. /*
  1215. * Add any public methods here by assigning them to $class_name.prototype
  1216. */
  1217.  
  1218. $dc
  1219. * The setUp() method is called the first time
  1220. * an object of this class is constructed.
  1221. * @method setUp
  1222. */
  1223. $class_name.prototype.setUp = function () {
  1224. // put any code here
  1225. // overrides the Base class
  1226. };
  1227.  
  1228. module.exports = $class_name;
  1229. EOT;
  1230.  
  1231. // overwrite base class file if necessary, but not the class file
  1232. Db_Utils::saveTextFile($base_class_filename, $base_class_string);
  1233. $filenames[] = $base_class_filename;
  1234. Db_Utils::saveTextFile($js_base_class_filename, $js_base_class_string);
  1235. $filenames[] = $js_base_class_filename;
  1236. if (! file_exists($class_filename)) {
  1237. Db_Utils::saveTextFile($class_filename, $class_string);
  1238. $filenames[] = $class_filename;
  1239. }
  1240. if (! file_exists($js_class_filename)) {
  1241. Db_Utils::saveTextFile($js_class_filename, $js_class_string);
  1242. $filenames[] = $js_class_filename;
  1243. }
  1244. $table_classnames[] = $class_name;
  1245. $js_table_classes_string .= <<<EOT
  1246.  
  1247. $dc
  1248. * Link to $connectionName.$class_name_base model
  1249. * @property $class_name_base
  1250. * @type $connectionName.$class_name_base
  1251. */
  1252. Base.$class_name_base = Q.require('$connectionName/$class_name_base');
  1253.  
  1254. EOT;
  1255. }
  1256. // Generate the "module model" base class file
  1257. $table_classnames_exported = var_export($table_classnames, true);
  1258. $table_classnames_json = $pk_json_indented = str_replace(
  1259. array("[", ",", "]"),
  1260. array("[\n\t", ",\n\t", "\n]"),
  1261. json_encode($table_classnames)
  1262. );
  1263. if (!empty($connectionName)) {
  1264. $class_name = Db::generateTableClassName($connectionName);
  1265. $class_name_parts = explode('_', $class_name);
  1266. $class_filename = $directory.DS.implode(DS, $class_name_parts).'.php';
  1267. $base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.php';
  1268. $js_class_filename = $directory.DS.implode(DS, $class_name_parts).'.js';
  1269. $js_base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.js';
  1270. $js_base_class_require = 'Base'.'/'.implode('/', $class_name_parts);
  1271. $dbname =
  1272. // because table name can be {$prefix}_Q_plugin or {$prefix}_Q_app we need to know correct table name
  1273. $tables = $this->rawQuery(
  1274. "SELECT table_comment"
  1275. ." FROM INFORMATION_SCHEMA.TABLES"
  1276. ." WHERE table_schema = '{$this->dbname}' and table_name LIKE '{$prefix}Q_%'"
  1277. )->execute()->fetchAll(PDO::FETCH_NUM);
  1278. $model_comment = (!empty($tables[0]['table_comment']))
  1279. ? " * <br>{".$tables[0]['table_comment']."}\n"
  1280. : '';
  1281. $model_extras = is_readable($class_filename.'.inc') ? file_get_contents($class_filename.'.inc') : '';
  1282. $js_model_extras = is_readable($js_class_filename.'.inc') ? file_get_contents($js_class_filename.'.inc') : '';
  1283.  
  1284. $base_class_string = <<<EOT
  1285. <?php
  1286.  
  1287. $dc
  1288. * Autogenerated base class for the $connectionName model.
  1289. *
  1290. * Don't change this file, since it can be overwritten.
  1291. * Instead, change the $class_name.php file.
  1292. *
  1293. * @module $connectionName
  1294. */
  1295. $dc
  1296. * Base class for the $class_name model
  1297. * @class Base_$class_name
  1298. */
  1299. abstract class Base_$class_name
  1300. {
  1301. $dc
  1302. * The list of model classes
  1303. * @property \$table_classnames
  1304. * @type array
  1305. */
  1306. static \$table_classnames = $table_classnames_exported;
  1307. $class_extras
  1308. $dc
  1309. * This method calls Db.connect() using information stored in the configuration.
  1310. * If this has already been called, then the same db object is returned.
  1311. * @method db
  1312. * @return {Db_Interface} The database object
  1313. */
  1314. static function db()
  1315. {
  1316. return Db::connect('$connectionName');
  1317. }
  1318.  
  1319. $dc
  1320. * The connection name for the class
  1321. * @method connectionName
  1322. * @return {string} The name of the connection
  1323. */
  1324. static function connectionName()
  1325. {
  1326. return '$connectionName';
  1327. }
  1328. };
  1329. EOT;
  1330.  
  1331. $js_base_class_string = <<<EOT
  1332. $dc
  1333. * Autogenerated base class for the $connectionName model.
  1334. *
  1335. * Don't change this file, since it can be overwritten.
  1336. * Instead, change the $class_name.js file.
  1337. *
  1338. * @module $connectionName
  1339. */
  1340. var Q = require('Q');
  1341. var Db = Q.require('Db');
  1342. $js_class_extras
  1343. $dc
  1344. * Base class for the $class_name model
  1345. * @namespace Base
  1346. * @class $class_name
  1347. * @static
  1348. */
  1349. function Base () {
  1350. return this;
  1351. }
  1352. module.exports = Base;
  1353.  
  1354. $dc
  1355. * The list of model classes
  1356. * @property tableClasses
  1357. * @type array
  1358. */
  1359. Base.tableClasses = $table_classnames_json;
  1360.  
  1361. $dc
  1362. * This method calls Db.connect() using information stored in the configuration.
  1363. * If this has already been called, then the same db object is returned.
  1364. * @method db
  1365. * @return {Db} The database connection
  1366. */
  1367. Base.db = function () {
  1368. return Db.connect('$connectionName');
  1369. };
  1370.  
  1371. $dc
  1372. * The connection name for the class
  1373. * @method connectionName
  1374. * @return {string} The name of the connection
  1375. */
  1376. Base.connectionName = function() {
  1377. return '$connectionName';
  1378. };
  1379. $js_table_classes_string
  1380. EOT;
  1381.  
  1382. $class_string = <<<EOT
  1383. <?php
  1384. $dc
  1385. * $class_name_prefix model
  1386. $model_comment * @module $connectionName
  1387. * @main $connectionName
  1388. */
  1389. $dc
  1390. * Static methods for the $connectionName models.
  1391. * @class $class_name
  1392. * @extends Base_$class_name
  1393. */
  1394. abstract class $class_name extends Base_$class_name
  1395. {
  1396. /*
  1397. * This is where you would place all the static methods for the models,
  1398. * the ones that don't strongly pertain to a particular row or table.
  1399. * If file '$class_name.php.inc' exists, its content is included
  1400. * * * */
  1401. $model_extras
  1402. /* * * */
  1403. };
  1404. EOT;
  1405.  
  1406. $js_class_string = <<<EOT
  1407. $dc
  1408. * $class_name_prefix model
  1409. $model_comment * @module $connectionName
  1410. * @main $connectionName
  1411. */
  1412. var Q = require('Q');
  1413.  
  1414. $dc
  1415. * Static methods for the $class_name_prefix model
  1416. * @class $class_name_prefix
  1417. * @extends Base.$class_name_prefix
  1418. * @static
  1419. */
  1420. function $connectionName() { };
  1421. module.exports = $connectionName;
  1422.  
  1423. var Base_$connectionName = Q.require('$js_base_class_require');
  1424. Q.mixin($connectionName, Base_$connectionName);
  1425.  
  1426. /*
  1427. * This is where you would place all the static methods for the models,
  1428. * the ones that don't strongly pertain to a particular row or table.
  1429. * Just assign them as methods of the $connectionName object.
  1430. * If file '$class_name.js.inc' exists, its content is included
  1431. * * * */
  1432. $js_model_extras
  1433. /* * * */
  1434. EOT;
  1435.  
  1436. // overwrite base class file if necessary, but not the class file
  1437. Db_Utils::saveTextFile($base_class_filename, $base_class_string);
  1438. $filenames[] = $base_class_filename;
  1439. Db_Utils::saveTextFile($js_base_class_filename, $js_base_class_string);
  1440. $filenames[] = $js_base_class_filename;
  1441. if (! file_exists($class_filename)) {
  1442. $filenames[] = $class_filename;
  1443. Db_Utils::saveTextFile($class_filename, $class_string);
  1444. }
  1445. if (! file_exists($js_class_filename)) {
  1446. $filenames[] = $js_class_filename;
  1447. Db_Utils::saveTextFile($js_class_filename, $js_class_string);
  1448. }
  1449. }
  1450. $directoryLen = strlen($directory.DS);
  1451. foreach ($filenames as $i => $filename) {
  1452. $filenames[$i] = substr($filename, $directoryLen);
  1453. }
  1454. return $filenames;
  1455. }
  1456.  
  1457. /**
  1458. * Generates code for a base class for the model
  1459. * @method codeForModelBaseClass
  1460. * @param {string} $table The name of the table to generate the code for.
  1461. * @param {string} $directory The path of the directory in which to place the model code.
  1462. * @param {string} [$classname_prefix=''] The prefix to prepend to the generated class names
  1463. * @param {&string} [$class_name_base=null] If set, this is the class name that is used.
  1464. * If an unset variable is passed, it is filled with the
  1465. * class name that is ultimately chosen, without the $classname_prefix
  1466. * @param {string} [$prefix=null] Defaults to the prefix of the tables, as specified in the connection.
  1467. * Pass null here to use the default, or a string to override it.
  1468. * @param {&string} [$js_code=null] The javascript code for the base class
  1469. * @param {&string} [$table_comment=''] The comment from the MySQL table if any
  1470. * @return {string} The generated code for the class.
  1471. */
  1472. function codeForModelBaseClass (
  1473. $table_name,
  1474. $directory,
  1475. $classname_prefix = '',
  1476. &$class_name_base = null,
  1477. $prefix = null,
  1478. &$js_code = null,
  1479. &$table_comment = '')
  1480. {
  1481. $dc = '/**';
  1482. if (empty($table_name))
  1483. throw new Exception('table_name parameter is empty', - 2);
  1484. if (empty($directory))
  1485. throw new Exception('directory parameter is empty', - 3);
  1486. $connectionName = $this->connectionName();
  1487. $conn = Db::getConnection($connectionName);
  1488. if (!isset($prefix)) {
  1489. $prefix = empty($conn['prefix']) ? '' : $conn['prefix'];
  1490. }
  1491. if (!empty($prefix)) {
  1492. $prefix_len = strlen($prefix);
  1493. $table_name_base = substr($table_name, $prefix_len);
  1494. $table_name_prefix = substr($table_name, 0, $prefix_len);
  1495. if (empty($table_name_base) or $table_name_prefix != $prefix)
  1496. return ''; // no class generated
  1497. } else {
  1498. $table_name_base = $table_name;
  1499. }
  1500.  
  1501. if (empty($classname_prefix))
  1502. $classname_prefix = '';
  1503. if (!isset($class_name_base)) {
  1504. $class_name_base = Db::generateTableClassName($table_name_base);
  1505. }
  1506. $class_name = ucfirst($classname_prefix) . $class_name_base;
  1507. $table_cols = $this->rawQuery("SHOW FULL COLUMNS FROM $table_name")->execute()->fetchAll(PDO::FETCH_ASSOC);
  1508. $table_status = $this->rawQuery("SHOW TABLE STATUS WHERE Name = '$table_name'")->execute()->fetchAll(PDO::FETCH_COLUMN, 17);
  1509. $table_comment = (!empty($table_status[0])) ? " * <br>{$table_status[0]}\n" : '';
  1510. // Calculate primary key
  1511. $pk = array();
  1512. foreach ($table_cols as $table_col) {
  1513. if ($table_col['Key'] == 'PRI') {
  1514. $pk[] = $table_col['Field'];
  1515. }
  1516. }
  1517. $pk_exported = var_export($pk, true);
  1518. $pk_json = json_encode($pk);
  1519. // Calculate validation functions
  1520. $functions = array();
  1521. $js_functions = array();
  1522. $field_names = array();
  1523. $properties = array();
  1524. $js_properties = array();
  1525. $required_field_names = array();
  1526. $magic_field_names = array();
  1527. $defaults = array();
  1528. $comments = array();
  1529. foreach ($table_cols as $table_col) {
  1530. $is_magic_field = null;
  1531. $field_name = $table_col['Field'];
  1532. $field_names[] = $field_name;
  1533. $field_null = $table_col['Null'] == 'YES' ? true : false;
  1534. $field_nulls[] = $field_null;
  1535. $field_default = $table_col['Default'];
  1536. $comments[] = $table_col['Comment'];
  1537. $field_name_safe = preg_replace('/[^0-9a-zA-Z\_]/', '_', $field_name);
  1538. $auto_inc = (strpos($table_col['Extra'], 'auto_increment') !== false);
  1539. $type = $table_col['Type'];
  1540. $pieces = explode('(', $type);
  1541. if (isset($pieces[1])) {
  1542. $pieces2 = explode(')', $pieces[1]);
  1543. $pieces2_count = count($pieces2);
  1544. if ($pieces2_count > 2) { // could happen if enum's values have ")"
  1545. $pieces2 = array(
  1546. implode(')', array_slice($pieces2, 0, -1)),
  1547. end($pieces2)
  1548. );
  1549. }
  1550. }
  1551. $type_name = $pieces[0];
  1552. if (isset($pieces2)) {
  1553. $type_display_range = $pieces2[0];
  1554. $type_modifiers = $pieces2[1];
  1555. $type_unsigned = (strpos($type_modifiers, 'unsigned') !== false);
  1556. }
  1557. $isTextLike = false;
  1558. $isNumberLike = false;
  1559. $isTimeLike = false;
  1560. switch ($type_name) {
  1561. case 'tinyint':
  1562. $type_range_min = $type_unsigned ? 0 : - 128;
  1563. $type_range_max = $type_unsigned ? 255 : 127;
  1564. break;
  1565. case 'smallint':
  1566. $type_range_min = $type_unsigned ? 0 : - 32768;
  1567. $type_range_max = $type_unsigned ? 65535 : 32767;
  1568. break;
  1569. case 'mediumint':
  1570. $type_range_min = $type_unsigned ? 0 : - 8388608;
  1571. $type_range_max = $type_unsigned ? 16777215 : 8388607;
  1572. break;
  1573. case 'int':
  1574. $type_range_min = $type_unsigned ? 0 : - 2147483648;
  1575. $type_range_max = $type_unsigned ? 4294967295 : 2147483647;
  1576. break;
  1577. case 'bigint':
  1578. $type_range_min = $type_unsigned ? 0 : - 9223372036854775808;
  1579. $type_range_max = $type_unsigned ? 18446744073709551615 : 9223372036854775807;
  1580. break;
  1581. case 'tinytext':
  1582. case 'tinyblob':
  1583. $type_display_range = 255;
  1584. break;
  1585. case 'text':
  1586. case 'blob':
  1587. $type_display_range = 65535;
  1588. break;
  1589. case 'mediumtext':
  1590. case 'mediumblob':
  1591. $type_display_range = 16777216;
  1592. break;
  1593. case 'longtext':
  1594. case 'longblob':
  1595. $type_display_range = 4294967296;
  1596. break;
  1597. }
  1598. $field_name_exported = var_export($field_name, true);
  1599. $null_check = $field_null ? "if (!isset(\$value)) {\n\t\t\treturn array($field_name_exported, \$value);\n\t\t}\n\t\t" : '';
  1600. $null_fix = $field_null ? '' : "if (!isset(\$value)) {\n\t\t\t\$value='';\n\t\t}\n\t\t";
  1601. $dbe_check = "if (\$value instanceof Db_Expression) {\n\t\t\treturn array($field_name_exported, \$value);\n\t\t}\n\t\t";
  1602. $js_null_check = $field_null ? "if (value == undefined) return value;\n\t\t" : '';
  1603. $js_null_fix = $field_null ? '' : "if (value == null) {\n\t\t\tvalue='';\n\t\t}\n\t\t";
  1604. $js_dbe_check = "if (value instanceof Db.Expression) return value;\n\t\t";
  1605. if (! isset($functions["beforeSet_$field_name_safe"]))
  1606. $functions["beforeSet_$field_name_safe"] = array();
  1607. if (! isset($js_functions["beforeSet_$field_name_safe"]))
  1608. $js_functions["beforeSet_$field_name_safe"] = array();
  1609. $type_name_lower = strtolower($type_name);
  1610. switch ($type_name_lower) {
  1611. case 'tinyint':
  1612. case 'smallint':
  1613. case 'int':
  1614. case 'mediumint':
  1615. case 'bigint':
  1616. $isNumberLike = true;
  1617. $properties[]="integer $field_name";
  1618. $js_properties[] = "Integer $field_name";
  1619. $functions["maxSize_$field_name_safe"]['comment'] = <<<EOT
  1620. $dc
  1621. * @method maxSize_$field_name_safe
  1622. * Returns the maximum integer that can be assigned to the $field_name field
  1623. * @return {integer}
  1624. */
  1625. EOT;
  1626. $functions["maxSize_$field_name_safe"]['args'] = '';
  1627. $functions["maxSize_$field_name_safe"]['return_statement'] = <<<EOT
  1628. return $type_range_max;
  1629. EOT;
  1630. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1631. {$null_check}{$dbe_check}if (!is_numeric(\$value) or floor(\$value) != \$value)
  1632. throw new Exception('Non-integer value being assigned to '.\$this->getTable().".$field_name");
  1633. \$value = intval(\$value);
  1634. if (\$value < $type_range_min or \$value > $type_range_max) {
  1635. \$json = json_encode(\$value);
  1636. throw new Exception("Out-of-range value \$json being assigned to ".\$this->getTable().".$field_name");
  1637. }
  1638. EOT;
  1639. $functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1640. $dc
  1641. * Method is called before setting the field and verifies if integer value falls within allowed limits
  1642. * @method beforeSet_$field_name_safe
  1643. * @param {integer} \$value
  1644. * @return {array} An array of field name and value
  1645. * @throws {Exception} An exception is thrown if \$value is not integer or does not fit in allowed range
  1646. */
  1647. EOT;
  1648. $js_functions["maxSize_$field_name_safe"]['comment'] = <<<EOT
  1649. $dc
  1650. * Returns the maximum integer that can be assigned to the $field_name field
  1651. * @return {integer}
  1652. */
  1653. EOT;
  1654. $js_functions["maxSize_$field_name_safe"]['args'] = '';
  1655. $js_functions["maxSize_$field_name_safe"]['return_statement'] = <<<EOT
  1656. return $type_range_max;
  1657. EOT;
  1658. $js_functions["beforeSet_$field_name_safe"][] = <<<EOT
  1659. {$js_null_check}{$js_dbe_check}value = Number(value);
  1660. if (isNaN(value) || Math.floor(value) != value)
  1661. throw new Error('Non-integer value being assigned to '+this.table()+".$field_name");
  1662. if (value < $type_range_min || value > $type_range_max)
  1663. throw new Error("Out-of-range value "+JSON.stringify(value)+" being assigned to "+this.table()+".$field_name");
  1664. EOT;
  1665. $js_functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1666. $dc
  1667. * Method is called before setting the field and verifies if integer value falls within allowed limits
  1668. * @method beforeSet_$field_name_safe
  1669. * @param {integer} value
  1670. * @return {integer} The value
  1671. * @throws {Error} An exception is thrown if 'value' is not integer or does not fit in allowed range
  1672. */
  1673. EOT;
  1674. $default = isset($table_col['Default'])
  1675. ? $table_col['Default']
  1676. : ($field_null ? null : 0);
  1677. $defaults[] = json_encode((int)$default);
  1678. break;
  1679.  
  1680. case 'enum':
  1681. $properties[]="string $field_name";
  1682. $js_properties[] = "String $field_name";
  1683. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1684. {$null_check}{$dbe_check}if (!in_array(\$value, array($type_display_range)))
  1685. throw new Exception("Out-of-range value '\$value' being assigned to ".\$this->getTable().".$field_name");
  1686. EOT;
  1687. $functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1688. $dc
  1689. * Method is called before setting the field and verifies if value belongs to enum values list
  1690. * @method beforeSet_$field_name_safe
  1691. * @param {string} \$value
  1692. * @return {array} An array of field name and value
  1693. * @throws {Exception} An exception is thrown if \$value does not belong to enum values list
  1694. */
  1695. EOT;
  1696. $js_functions["beforeSet_$field_name_safe"][] = <<<EOT
  1697. {$js_null_check}{$js_dbe_check}if ([$type_display_range].indexOf(value) < 0)
  1698. throw new Error("Out-of-range value "+JSON.stringify(value)+" being assigned to "+this.table()+".$field_name");
  1699. EOT;
  1700. $js_functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1701. $dc
  1702. * Method is called before setting the field and verifies if value belongs to enum values list
  1703. * @method beforeSet_$field_name_safe
  1704. * @param {string} value
  1705. * @return {string} The value
  1706. * @throws {Error} An exception is thrown if 'value' does not belong to enum values list
  1707. */
  1708. EOT;
  1709. $default = isset($table_col['Default'])
  1710. ? $table_col['Default']
  1711. : null;
  1712. $defaults[] = json_encode($default);
  1713. break;
  1714. case 'char':
  1715. case 'varchar':
  1716. case 'binary':
  1717. case 'varbinary':
  1718. case 'tinytext':
  1719. case 'text':
  1720. case 'mediumtext':
  1721. case 'longtext':
  1722. case 'tinyblob':
  1723. case 'blob':
  1724. case 'mediumblob':
  1725. case 'longblob':
  1726. $isTextLike = true;
  1727. $isBinary = in_array($type_name_lower, array(
  1728. 'binary', 'varbinary',
  1729. 'tinyblob', 'blob', 'mediumblob', 'longblob'
  1730. ));
  1731. $orBuffer1 = $isBinary ? "|Buffer" : "";
  1732. $properties[]="string $field_name";
  1733. $js_properties[] = "String$orBuffer1 $field_name";
  1734. $functions["maxSize_$field_name_safe"]['comment'] = <<<EOT
  1735. $dc
  1736. * Returns the maximum string length that can be assigned to the $field_name field
  1737. * @return {integer}
  1738. */
  1739. EOT;
  1740. $functions["maxSize_$field_name_safe"]['args'] = '';
  1741. $functions["maxSize_$field_name_safe"]['return_statement'] = <<<EOT
  1742. return $type_display_range;
  1743. EOT;
  1744. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1745. {$null_check}{$null_fix}{$dbe_check}if (!is_string(\$value) and !is_numeric(\$value))
  1746. throw new Exception('Must pass a string to '.\$this->getTable().".$field_name");
  1747.  
  1748. EOT;
  1749. if ($type_display_range and $type_display_range < $this->maxCheckStrlen) {
  1750. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1751. if (strlen(\$value) > $type_display_range)
  1752. throw new Exception('Exceedingly long value being assigned to '.\$this->getTable().".$field_name");
  1753. EOT;
  1754. }
  1755. $functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1756. $dc
  1757. * Method is called before setting the field and verifies if value is string of length within acceptable limit.
  1758. * Optionally accept numeric value which is converted to string
  1759. * @method beforeSet_$field_name
  1760. * @param {string} \$value
  1761. * @return {array} An array of field name and value
  1762. * @throws {Exception} An exception is thrown if \$value is not string or is exceedingly long
  1763. */
  1764. EOT;
  1765. $js_functions["maxSize_$field_name_safe"]['comment'] = <<<EOT
  1766. $dc
  1767. * Returns the maximum string length that can be assigned to the $field_name field
  1768. * @return {integer}
  1769. */
  1770. EOT;
  1771. $js_functions["maxSize_$field_name_safe"]['args'] = '';
  1772. $js_functions["maxSize_$field_name_safe"]['return_statement'] = <<<EOT
  1773. return $type_display_range;
  1774. EOT;
  1775. $bufferCheck = $isBinary ? " && !(value instanceof Buffer)" : "";
  1776. $orBuffer2 = $isBinary ? " or Buffer" : "";
  1777. $js_functions["beforeSet_$field_name_safe"][] = <<<EOT
  1778. {$js_null_check}{$js_null_fix}{$js_dbe_check}if (typeof value !== "string" && typeof value !== "number"$bufferCheck)
  1779. throw new Error('Must pass a String$orBuffer2 to '+this.table()+".$field_name");
  1780. if (typeof value === "string" && value.length > $type_display_range)
  1781. throw new Error('Exceedingly long value being assigned to '+this.table()+".$field_name");
  1782. EOT;
  1783. $js_functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1784. $dc
  1785. * Method is called before setting the field and verifies if value is string of length within acceptable limit.
  1786. * Optionally accept numeric value which is converted to string
  1787. * @method beforeSet_$field_name_safe
  1788. * @param {string} value
  1789. * @return {string} The value
  1790. * @throws {Error} An exception is thrown if 'value' is not string or is exceedingly long
  1791. */
  1792. EOT;
  1793. $default = isset($table_col['Default'])
  1794. ? $table_col['Default']
  1795. : ($field_null ? null : '');
  1796. $defaults[] = json_encode($default);
  1797. break;
  1798. case 'date':
  1799. $isTimeLike = true;
  1800. $properties[]="string|Db_Expression $field_name";
  1801. $js_properties[] = "String|Db.Expression $field_name";
  1802. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1803. {$null_check}{$dbe_check}\$date = date_parse(\$value);
  1804. if (!empty(\$date['errors'])) {
  1805. \$json = json_encode(\$value);
  1806. throw new Exception("Date \$json in incorrect format being assigned to ".\$this->getTable().".$field_name");
  1807. }
  1808. \$value = date("Y-m-d H:i:s", strtotime(\$value));
  1809. \$date = date_parse(\$value);
  1810. foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as \$v) {
  1811. \$\$v = \$date[\$v];
  1812. }
  1813. \$value = sprintf("%04d-%02d-%02d", \$year, \$month, \$day);
  1814. EOT;
  1815. $functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1816. $dc
  1817. * Method is called before setting the field and normalize the date string
  1818. * @method beforeSet_$field_name_safe
  1819. * @param {string} \$value
  1820. * @return {array} An array of field name and value
  1821. * @throws {Exception} An exception is thrown if \$value does not represent valid date
  1822. */
  1823. EOT;
  1824. $js_functions["beforeSet_$field_name_safe"][] = <<<EOT
  1825. {$js_null_check}{$js_dbe_check}value = (value instanceof Date) ? Base.db().toDateTime(value) : value;
  1826. EOT;
  1827. $js_functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1828. $dc
  1829. * Method is called before setting the field
  1830. * @method beforeSet_$field_name_safe
  1831. * @param {String} value
  1832. * @return {Date|Db.Expression} If 'value' is not Db.Expression the current date is returned
  1833. */
  1834. EOT;
  1835. $default = isset($table_col['Default'])
  1836. ? $table_col['Default']
  1837. : ($field_null ? null : '');
  1838. if ($default === 'CURRENT_TIMESTAMP'
  1839. or strpos($default, '(') !== false) {
  1840. $default = "new Db_Expression($default)";
  1841. }
  1842. $isExpression = (
  1843. $default === 'CURRENT_TIMESTAMP'
  1844. or strpos($default, '(') !== false
  1845. );
  1846. $defaults[] = $isExpression
  1847. ? 'new Db_Expression(' . json_encode($default) . ')'
  1848. : json_encode($default);
  1849. break;
  1850. case 'datetime':
  1851. case 'timestamp':
  1852. $isTimeLike = true;
  1853. $properties[]="string|Db_Expression $field_name";
  1854. $js_properties[] = "String|Db.Expression $field_name";
  1855. $possibleMagicFields = array('insertedTime', 'updatedTime', 'created_time', 'updated_time');
  1856. $possibleMagicInsertFields = array('insertedTime', 'created_time');
  1857. if (in_array($field_name, $possibleMagicFields)) {
  1858. if (!in_array($field_name, $possibleMagicInsertFields)
  1859. or !isset($field_default)) {
  1860. $magic_field_names[] = $field_name;
  1861. $is_magic_field = true;
  1862. }
  1863. }
  1864. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1865. {$null_check}{$dbe_check}if (\$value instanceof DateTime) {
  1866. \$value = \$value->getTimestamp();
  1867. }
  1868. if (is_numeric(\$value)) {
  1869. \$newDateTime = new DateTime();
  1870. \$datetime = \$newDateTime->setTimestamp(\$value);
  1871. } else {
  1872. \$datetime = new DateTime(\$value);
  1873. }
  1874. \$value = \$datetime->format("Y-m-d H:i:s");
  1875. EOT;
  1876. $functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1877. $dc
  1878. * Method is called before setting the field and normalize the DateTime string
  1879. * @method beforeSet_$field_name_safe
  1880. * @param {string} \$value
  1881. * @return {array} An array of field name and value
  1882. * @throws {Exception} An exception is thrown if \$value does not represent valid DateTime
  1883. */
  1884. EOT;
  1885. $js_functions["beforeSet_$field_name_safe"][] = <<<EOT
  1886. {$js_null_check}{$js_dbe_check}if (typeof value !== 'object' && !isNaN(value)) {
  1887. value = parseInt(value);
  1888. value = new Date(value < 10000000000 ? value * 1000 : value);
  1889. }
  1890. value = (value instanceof Date) ? Base.db().toDateTime(value) : value;
  1891. EOT;
  1892. $js_functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1893. $dc
  1894. * Method is called before setting the field
  1895. * @method beforeSet_$field_name_safe
  1896. * @param {String} value
  1897. * @return {Date|Db.Expression} If 'value' is not Db.Expression the current date is returned
  1898. */
  1899. EOT;
  1900. $default = isset($table_col['Default'])
  1901. ? $table_col['Default']
  1902. : ($field_null ? null : '');
  1903. $isExpression = (
  1904. $default === 'CURRENT_TIMESTAMP'
  1905. or strpos($default, '(') !== false
  1906. );
  1907. $defaults[] = $isExpression
  1908. ? 'new Db_Expression(' . json_encode($default) . ')'
  1909. : json_encode($default);
  1910. break;
  1911.  
  1912. case 'numeric':
  1913. case 'decimal':
  1914. case 'float':
  1915. case 'double':
  1916. $isNumberLike = true;
  1917. $properties[]="float $field_name";
  1918. $js_properties[] = "Number $field_name";
  1919. $functions["beforeSet_$field_name_safe"][] = <<<EOT
  1920. {$null_check}{$dbe_check}if (!is_numeric(\$value))
  1921. throw new Exception('Non-numeric value being assigned to '.\$this->getTable().".$field_name");
  1922. \$value = floatval(\$value);
  1923. EOT;
  1924. $js_functions["beforeSet_$field_name_safe"][] = <<<EOT
  1925. {$js_null_check}{$js_dbe_check}value = Number(value);
  1926. if (isNaN(value))
  1927. throw new Error('Non-number value being assigned to '+this.table()+".$field_name");
  1928. EOT;
  1929. $js_functions["beforeSet_$field_name_safe"]['comment'] = <<<EOT
  1930. $dc
  1931. * Method is called before setting the field to verify if value is a number
  1932. * @method beforeSet_$field_name_safe
  1933. * @param {integer} value
  1934. * @return {integer} The value
  1935. * @throws {Error} If 'value' is not number
  1936. */
  1937. EOT;
  1938. $default = isset($table_col['Default'])
  1939. ? $table_col['Default']
  1940. : ($field_null ? null : 0);
  1941. $defaults[] = json_encode((double)$default);
  1942. break;
  1943.  
  1944. default:
  1945. $properties[]="mixed $field_name";
  1946. $js_properties[] = "mixed $field_name";
  1947. $default = isset($table_col['Default'])
  1948. ? $table_col['Default']
  1949. : ($field_null ? null : '');
  1950. $defaults[] = json_encode($default);
  1951. break;
  1952. }
  1953. if (! empty($functions["beforeSet_$field_name_safe"])) {
  1954. $functions["beforeSet_$field_name_safe"]['return_statement'] = <<<EOT
  1955. return array('$field_name', \$value);
  1956. EOT;
  1957. }
  1958. if (! empty($js_functions["beforeSet_$field_name_safe"])) {
  1959. $js_functions["beforeSet_$field_name_safe"]['return_statement'] = <<<EOT
  1960. return value;
  1961. EOT;
  1962. }
  1963. if (! $field_null and ! $is_magic_field
  1964. and ((!$isNumberLike and !$isTextLike) or in_array($field_name, $pk))
  1965. and ! $auto_inc and !isset($field_default)) {
  1966. $required_field_names[] = $field_name_exported;
  1967. }
  1968. $columnInfo = array(
  1969. array($type_name, $type_display_range, $type_modifiers, $type_unsigned),
  1970. $field_null,
  1971. $table_col['Key'],
  1972. $table_col['Default']
  1973. );
  1974. $columnInfo_php = var_export($columnInfo, true);
  1975. $columnInfo_js = json_encode($columnInfo);
  1976. $functions["column_$field_name_safe"]['comment'] = <<<EOT
  1977. $dc
  1978. * Returns schema information for $field_name column
  1979. * @return {array} [[typeName, displayRange, modifiers, unsigned], isNull, key, default]
  1980. */
  1981. EOT;
  1982. $functions["column_$field_name_safe"]['static'] = true;
  1983. $functions["column_$field_name_safe"]['args'] = '';
  1984. $functions["column_$field_name_safe"]['return_statement'] = <<<EOT
  1985. return $columnInfo_php;
  1986. EOT;
  1987. $js_functions["column_$field_name_safe"]['static'] = true;
  1988. $js_functions["column_$field_name_safe"]['comment'] = <<<EOT
  1989. $dc
  1990. * Returns schema information for $field_name column
  1991. * @return {array} [[typeName, displayRange, modifiers, unsigned], isNull, key, default]
  1992. */
  1993. EOT;
  1994. $js_functions["column_$field_name_safe"]['args'] = '';
  1995. $js_functions["column_$field_name_safe"]['return_statement'] = <<<EOT
  1996. return $columnInfo_js;
  1997. EOT;
  1998. }
  1999. $field_names_json = json_encode($field_names);
  2000. $field_names_json_indented = str_replace(
  2001. array("[", ",", "]"),
  2002. array("[\n\t\t", ",\n\t\t", "\n\t]"),
  2003. $field_names_json
  2004. );
  2005. $field_names_exported = "\$this->fieldNames()";
  2006. $functions['beforeSave'] = array();
  2007. $js_functions['beforeSave'] = array();
  2008. if ($required_field_names) {
  2009. $required_fields_string = implode(',', $required_field_names);
  2010. $beforeSave_code = <<<EOT
  2011. if (!\$this->retrieved) {
  2012. \$table = \$this->getTable();
  2013. foreach (array($required_fields_string) as \$name) {
  2014. if (!isset(\$value[\$name])) {
  2015. throw new Exception("the field \$table.\$name needs a value, because it is NOT NULL, not auto_increment, and lacks a default value.");
  2016. }
  2017. }
  2018. }
  2019. EOT;
  2020. $js_beforeSave_code = <<<EOT
  2021. var fields = [$required_fields_string], i;
  2022. if (!this._retrieved) {
  2023. var table = this.table();
  2024. for (i=0; i<fields.length; i++) {
  2025. if (this.fields[fields[i]] === undefined) {
  2026. throw new Error("the field "+table+"."+fields[i]+" needs a value, because it is NOT NULL, not auto_increment, and lacks a default value.");
  2027. }
  2028. }
  2029. }
  2030. EOT;
  2031. $return_statement = <<<EOT
  2032. return \$value;
  2033. EOT;
  2034. $js_return_statement = <<<EOT
  2035. return value;
  2036. EOT;
  2037. $functions["beforeSave"][] = $beforeSave_code;
  2038. $functions['beforeSave']['return_statement'] = $return_statement;
  2039. $functions['beforeSave']['comment'] = <<<EOT
  2040. $dc
  2041. * Check if mandatory fields are set and updates 'magic fields' with appropriate values
  2042. * @method beforeSave
  2043. * @param {array} \$value The array of fields
  2044. * @return {array}
  2045. * @throws {Exception} If mandatory field is not set
  2046. */
  2047. EOT;
  2048. $js_functions["beforeSave"][] = $js_beforeSave_code;
  2049. $js_functions['beforeSave']['return_statement'] = $js_return_statement;
  2050. $js_functions['beforeSave']['comment'] = <<<EOT
  2051. $dc
  2052. * Check if mandatory fields are set and updates 'magic fields' with appropriate values
  2053. * @method beforeSave
  2054. * @param {Object} value The object of fields
  2055. * @param {Function} callback Call this callback if you return null
  2056. * @return {Object|null} Return the fields, modified if necessary. If you return null, then you should call the callback(err, modifiedFields)
  2057. * @throws {Error} If e.g. mandatory field is not set or a bad values are supplied
  2058. */
  2059. EOT;
  2060. }
  2061.  
  2062. //$functions['beforeSave'] = array();
  2063. if (count($magic_field_names) > 0) {
  2064. $beforeSave_code = '';
  2065. $js_beforeSave_code = '';
  2066. foreach (array('created_time', 'insertedTime') as $cmf) {
  2067. if (in_array($cmf, $magic_field_names)) {
  2068. $beforeSave_code .= <<<EOT
  2069.  
  2070. if (!\$this->retrieved and !isset(\$value['$cmf'])) {
  2071. \$this->$cmf = \$value['$cmf'] = new Db_Expression('CURRENT_TIMESTAMP');
  2072. }
  2073.  
  2074. EOT;
  2075. $js_beforeSave_code .= <<<EOT
  2076.  
  2077. if (!this._retrieved && !value['$cmf']) {
  2078. this['$cmf'] = value['$cmf'] = new Db.Expression('CURRENT_TIMESTAMP');
  2079. }
  2080. EOT;
  2081. break;
  2082. }
  2083. }
  2084. foreach (array('updated_time', 'updatedTime') as $umf) {
  2085. if (in_array($umf, $magic_field_names)) {
  2086. $beforeSave_code .= <<<EOT
  2087. // convention: we'll have $umf = $cmf if just created.
  2088. \$this->$umf = \$value['$umf'] = new Db_Expression('CURRENT_TIMESTAMP');
  2089. EOT;
  2090. $js_beforeSave_code .= <<<EOT
  2091.  
  2092. // convention: we'll have $umf = $cmf if just created.
  2093. this['$umf'] = value['$umf'] = new Db.Expression('CURRENT_TIMESTAMP');
  2094. EOT;
  2095. break;
  2096. }
  2097. }
  2098. $return_statement = <<<EOT
  2099. return \$value;
  2100. EOT;
  2101. $js_return_statement = <<<EOT
  2102. return value;
  2103. EOT;
  2104. $functions['beforeSave'][] = $beforeSave_code;
  2105. $functions['beforeSave']['return_statement'] = $return_statement;
  2106. $js_functions['beforeSave'][] = $js_beforeSave_code;
  2107. $js_functions['beforeSave']['return_statement'] = $js_return_statement;
  2108. }
  2109. $functions['fieldNames'] = array();
  2110. $fieldNames_exported = Db_Utils::var_export($field_names);
  2111. $fieldNames_code = <<<EOT
  2112. \$field_names = $fieldNames_exported;
  2113. \$result = \$field_names;
  2114. if (!empty(\$table_alias)) {
  2115. \$temp = array();
  2116. foreach (\$result as \$field_name)
  2117. \$temp[] = \$table_alias . '.' . \$field_name;
  2118. \$result = \$temp;
  2119. }
  2120. if (!empty(\$field_alias_prefix)) {
  2121. \$temp = array();
  2122. reset(\$field_names);
  2123. foreach (\$result as \$field_name) {
  2124. \$temp[\$field_alias_prefix . current(\$field_names)] = \$field_name;
  2125. next(\$field_names);
  2126. }
  2127. \$result = \$temp;
  2128. }
  2129. EOT;
  2130. $return_statement = <<<EOT
  2131. return \$result;
  2132. EOT;
  2133. $functions['fieldNames'][] = $fieldNames_code;
  2134. $functions['fieldNames']['return_statement'] = $return_statement;
  2135. $functions['fieldNames']['args'] = '$table_alias = null, $field_alias_prefix = null';
  2136. $functions['fieldNames']['static'] = true;
  2137. $functions['fieldNames']['comment'] = <<<EOT
  2138. $dc
  2139. * Retrieves field names for class table
  2140. * @method fieldNames
  2141. * @static
  2142. * @param {string} [\$table_alias=null] If set, the alieas is added to each field
  2143. * @param {string} [\$field_alias_prefix=null] If set, the method returns associative array of ('prefixed field' => 'field') pairs
  2144. * @return {array} An array of field names
  2145. */
  2146. EOT;
  2147. $functions_code = array();
  2148. foreach ($functions as $func_name => $func_code) {
  2149. $func_args = isset($func_code['args']) ? $func_code['args'] : '$value';
  2150. $func_modifiers = !empty($func_code['static']) ? 'static ' : '';
  2151. $func_code_string = isset($func_code['comment']) ? $func_code['comment']."\n" : '';
  2152. $func_code_string .= <<<EOT
  2153. {$func_modifiers}function $func_name($func_args)
  2154. {
  2155.  
  2156. EOT;
  2157. if (is_array($func_code) and ! empty($func_code)) {
  2158. foreach ($func_code as $key => $code_tool) {
  2159. if (is_string($key))
  2160. continue;
  2161. $func_code_string .= $code_tool;
  2162. }
  2163. $func_code_string .= "\n" . $func_code['return_statement'];
  2164. }
  2165. $func_code_string .= <<<EOT
  2166. }
  2167. EOT;
  2168. if (! empty($func_code))
  2169. $functions_code[] = $func_code_string;
  2170. }
  2171. $functions_string = implode("\n\n", $functions_code);
  2172. foreach ($js_functions as $func_name => $func_code) {
  2173. $func_args = isset($func_code['args']) ? $func_code['args'] : 'value';
  2174. $instance = isset($func_code['instance']) ? '.prototype' : '';
  2175. $func_code_string = isset($func_code['comment']) ? $func_code['comment']."\n" : '';
  2176. $prototype = empty($func_code['static']) ? 'prototype.' : '';
  2177. $func_code_string .= <<<EOT
  2178. Base.$prototype$func_name = function ($func_args) {
  2179.  
  2180. EOT;
  2181. if (is_array($func_code) and ! empty($func_code)) {
  2182. foreach ($func_code as $key => $code_tool) {
  2183. if (is_string($key))
  2184. continue;
  2185. $func_code_string .= $code_tool;
  2186. }
  2187. $func_code_string .= "\n" . $func_code['return_statement'];
  2188. }
  2189. $func_code_string .= <<<EOT
  2190.  
  2191. };
  2192. EOT;
  2193. if (! empty($func_code))
  2194. $js_functions_code[] = $func_code_string;
  2195. }
  2196. $js_functions_string = implode("\n\n", $js_functions_code);
  2197.  
  2198. $pk_exported_indented = str_replace("\n", "\n\t\t\t", $pk_exported);
  2199. $pk_json_indented = str_replace(
  2200. array("[", ",", "]"),
  2201. array("[\n\t\t", ",\n\t\t", "\n\t]"),
  2202. $pk_json
  2203. );
  2204. $connectionName_var = var_export($connectionName, true);
  2205. $class_name_var = var_export($class_name, true);
  2206.  
  2207. $class_name_prefix = rtrim(ucfirst($classname_prefix), "._");
  2208.  
  2209. $properties2 = array();
  2210. $js_properties2 = array();
  2211. foreach ($properties as $k => $v) {
  2212. $tmp = explode(' ', $v);
  2213. $default = $defaults[$k];
  2214. $comment = str_replace('*/', '**', $comments[$k]);
  2215. $properties[$k] = <<<EOT
  2216. $dc
  2217. * @property \${$tmp[1]}
  2218. * @type $tmp[0]
  2219. * @default $default
  2220. * $comment
  2221. */
  2222. EOT;
  2223. $required = !$field_nulls[$k] && $default == '"null"';
  2224. $properties2[$k] = $required
  2225. ? " * @param {".$tmp[0]."} \$fields.".$tmp[1]
  2226. : " * @param {".$tmp[0]."} [\$fields.".$tmp[1]."] defaults to $default";
  2227. }
  2228. foreach ($js_properties as $k => $v) {
  2229. $tmp = explode(' ', $v);
  2230. $default = $defaults[$k];
  2231. $comment = str_replace('*/', '**', $comments[$k]);
  2232. $js_properties[$k] = <<<EOT
  2233. $dc
  2234. * @property $tmp[1]
  2235. * @type $tmp[0]
  2236. * @default $default
  2237. * $comment
  2238. */
  2239. EOT;
  2240. $required = !$field_nulls[$k] && $default == '"null"';
  2241. $js_properties2[$k] = $required_fields_string
  2242. ? " * @param {".$tmp[0]."} \$fields.".$tmp[1]
  2243. : " * @param {".$tmp[0]."} [\$fields.".$tmp[1]."] defaults to $default";
  2244. }
  2245. $field_hints = implode("\n", $properties);
  2246. $field_hints2 = implode("\n", $properties2);
  2247. $js_field_hints = implode("\n", $js_properties);
  2248. $js_field_hints2 = implode("\n", $properties2);
  2249. // Here is the base class:
  2250. $base_class_string = <<<EOT
  2251. <?php
  2252.  
  2253. $dc
  2254. * Autogenerated base class representing $table_name_base rows
  2255. * in the $connectionName database.
  2256. *
  2257. * Don't change this file, since it can be overwritten.
  2258. * Instead, change the $class_name.php file.
  2259. *
  2260. * @module $connectionName
  2261. */
  2262. $dc
  2263. * Base class representing '$class_name_base' rows in the '$connectionName' database
  2264. * @class Base_$class_name
  2265. * @extends Db_Row
  2266. *
  2267. * @param {array} [\$fields=array()] The fields values to initialize table row as
  2268. * an associative array of \$column => \$value pairs
  2269. $field_hints2
  2270. */
  2271. abstract class Base_$class_name extends Db_Row
  2272. {
  2273. $field_hints
  2274. $dc
  2275. * The setUp() method is called the first time
  2276. * an object of this class is constructed.
  2277. * @method setUp
  2278. */
  2279. function setUp()
  2280. {
  2281. \$this->setDb(self::db());
  2282. \$this->setTable(self::table());
  2283. \$this->setPrimaryKey(
  2284. $pk_exported_indented
  2285. );
  2286. }
  2287.  
  2288. $dc
  2289. * Connects to database
  2290. * @method db
  2291. * @static
  2292. * @return {Db_Interface} The database object
  2293. */
  2294. static function db()
  2295. {
  2296. return Db::connect($connectionName_var);
  2297. }
  2298.  
  2299. $dc
  2300. * Retrieve the table name to use in SQL statement
  2301. * @method table
  2302. * @static
  2303. * @param {boolean} [\$with_db_name=true] Indicates wheather table name should contain the database name
  2304. * @return {string|Db_Expression} The table name as string optionally without database name if no table sharding
  2305. * was started or Db_Expression class with prefix and database name templates is table was sharded
  2306. */
  2307. static function table(\$with_db_name = true)
  2308. {
  2309. if (Q_Config::get('Db', 'connections', '$connectionName', 'indexes', '$class_name_base', false)) {
  2310. return new Db_Expression((\$with_db_name ? '{\$dbname}.' : '').'{\$prefix}'.'$table_name_base');
  2311. } else {
  2312. \$conn = Db::getConnection($connectionName_var);
  2313. \$prefix = empty(\$conn['prefix']) ? '' : \$conn['prefix'];
  2314. \$table_name = \$prefix . '$table_name_base';
  2315. if (!\$with_db_name)
  2316. return \$table_name;
  2317. \$db = Db::connect($connectionName_var);
  2318. return \$db->dbName().'.'.\$table_name;
  2319. }
  2320. }
  2321. $dc
  2322. * The connection name for the class
  2323. * @method connectionName
  2324. * @static
  2325. * @return {string} The name of the connection
  2326. */
  2327. static function connectionName()
  2328. {
  2329. return $connectionName_var;
  2330. }
  2331.  
  2332. $dc
  2333. * Create SELECT query to the class table
  2334. * @method select
  2335. * @static
  2336. * @param {string|array} [\$fields=null] The fields as strings, or array of alias=>field.
  2337. * The default is to return all fields of the table.
  2338. * @param {string|array} [\$alias=null] The tables as strings, or array of alias=>table.
  2339. * @return {Db_Query_Mysql} The generated query
  2340. */
  2341. static function select(\$fields=null, \$alias = null)
  2342. {
  2343. if (!isset(\$fields)) {
  2344. \$fieldNames = array();
  2345. foreach (self::fieldNames() as \$fn) {
  2346. \$fieldNames[] = \$fn;
  2347. }
  2348. \$fields = implode(',', \$fieldNames);
  2349. }
  2350. if (!isset(\$alias)) \$alias = '';
  2351. \$q = self::db()->select(\$fields, self::table().' '.\$alias);
  2352. \$q->className = $class_name_var;
  2353. return \$q;
  2354. }
  2355.  
  2356. $dc
  2357. * Create UPDATE query to the class table
  2358. * @method update
  2359. * @static
  2360. * @param {string} [\$alias=null] Table alias
  2361. * @return {Db_Query_Mysql} The generated query
  2362. */
  2363. static function update(\$alias = null)
  2364. {
  2365. if (!isset(\$alias)) \$alias = '';
  2366. \$q = self::db()->update(self::table().' '.\$alias);
  2367. \$q->className = $class_name_var;
  2368. return \$q;
  2369. }
  2370.  
  2371. $dc
  2372. * Create DELETE query to the class table
  2373. * @method delete
  2374. * @static
  2375. * @param {object} [\$table_using=null] If set, adds a USING clause with this table
  2376. * @param {string} [\$alias=null] Table alias
  2377. * @return {Db_Query_Mysql} The generated query
  2378. */
  2379. static function delete(\$table_using = null, \$alias = null)
  2380. {
  2381. if (!isset(\$alias)) \$alias = '';
  2382. \$q = self::db()->delete(self::table().' '.\$alias, \$table_using);
  2383. \$q->className = $class_name_var;
  2384. return \$q;
  2385. }
  2386.  
  2387. $dc
  2388. * Create INSERT query to the class table
  2389. * @method insert
  2390. * @static
  2391. * @param {object} [\$fields=array()] The fields as an associative array of column => value pairs
  2392. * @param {string} [\$alias=null] Table alias
  2393. * @return {Db_Query_Mysql} The generated query
  2394. */
  2395. static function insert(\$fields = array(), \$alias = null)
  2396. {
  2397. if (!isset(\$alias)) \$alias = '';
  2398. \$q = self::db()->insert(self::table().' '.\$alias, \$fields);
  2399. \$q->className = $class_name_var;
  2400. return \$q;
  2401. }
  2402. $dc
  2403. * Inserts multiple rows into a single table, preparing the statement only once,
  2404. * and executes all the queries.
  2405. * @method insertManyAndExecute
  2406. * @static
  2407. * @param {array} [\$rows=array()] The array of rows to insert.
  2408. * (The field names for the prepared statement are taken from the first row.)
  2409. * You cannot use Db_Expression objects here, because the function binds all parameters with PDO.
  2410. * @param {array} [\$options=array()]
  2411. * An associative array of options, including:
  2412. *
  2413. * * "chunkSize" {integer} The number of rows to insert at a time. defaults to 20.<br>
  2414. * * "onDuplicateKeyUpdate" {array} You can put an array of fieldname => value pairs here,
  2415. * which will add an ON DUPLICATE KEY UPDATE clause to the query.
  2416. *
  2417. */
  2418. static function insertManyAndExecute(\$rows = array(), \$options = array())
  2419. {
  2420. self::db()->insertManyAndExecute(
  2421. self::table(), \$rows,
  2422. array_merge(\$options, array('className' => $class_name_var))
  2423. );
  2424. }
  2425. $dc
  2426. * Create raw query with begin clause
  2427. * You'll have to specify shards yourself when calling execute().
  2428. * @method begin
  2429. * @static
  2430. * @param {string} [\$lockType=null] First parameter to pass to query->begin() function
  2431. * @return {Db_Query_Mysql} The generated query
  2432. */
  2433. static function begin(\$lockType = null)
  2434. {
  2435. \$q = self::db()->rawQuery('')->begin(\$lockType);
  2436. \$q->className = $class_name_var;
  2437. return \$q;
  2438. }
  2439. $dc
  2440. * Create raw query with commit clause
  2441. * You'll have to specify shards yourself when calling execute().
  2442. * @method commit
  2443. * @static
  2444. * @return {Db_Query_Mysql} The generated query
  2445. */
  2446. static function commit()
  2447. {
  2448. \$q = self::db()->rawQuery('')->commit();
  2449. \$q->className = $class_name_var;
  2450. return \$q;
  2451. }
  2452. $dc
  2453. * Create raw query with rollback clause
  2454. * @method rollback
  2455. * @static
  2456. * @param {array} \$criteria Can be used to target the rollback to some shards.
  2457. * Otherwise you'll have to specify shards yourself when calling execute().
  2458. * @return {Db_Query_Mysql} The generated query
  2459. */
  2460. static function rollback()
  2461. {
  2462. \$q = self::db()->rawQuery('')->rollback();
  2463. \$q->className = $class_name_var;
  2464. return \$q;
  2465. }
  2466. $functions_string
  2467. };
  2468. EOT;
  2469.  
  2470. // Set the JS code
  2471. $js_code = <<<EOT
  2472. $dc
  2473. * Autogenerated base class representing $table_name_base rows
  2474. * in the $connectionName database.
  2475. *
  2476. * Don't change this file, since it can be overwritten.
  2477. * Instead, change the $class_name_prefix/$class_name_base.js file.
  2478. *
  2479. * @module $connectionName
  2480. */
  2481.  
  2482. var Q = require('Q');
  2483. var Db = Q.require('Db');
  2484. var $connectionName = Q.require('$connectionName');
  2485. var Row = Q.require('Db/Row');
  2486.  
  2487. $dc
  2488. * Base class representing '$class_name_base' rows in the '$connectionName' database
  2489. * @namespace Base.$class_name_prefix
  2490. * @class $class_name_base
  2491. * @extends Db.Row
  2492. * @constructor
  2493. * @param {object} [fields={}] The fields values to initialize table row as
  2494. * an associative array of {column: value} pairs
  2495. $field_hints2
  2496. */
  2497. function Base (fields) {
  2498. Base.constructors.apply(this, arguments);
  2499. }
  2500.  
  2501. Q.mixin(Base, Row);
  2502.  
  2503. $js_field_hints
  2504.  
  2505. $dc
  2506. * This method calls Db.connect() using information stored in the configuration.
  2507. * If this has already been called, then the same db object is returned.
  2508. * @method db
  2509. * @return {Db} The database connection
  2510. */
  2511. Base.db = function () {
  2512. return $connectionName.db();
  2513. };
  2514.  
  2515. $dc
  2516. * Retrieve the table name to use in SQL statements
  2517. * @method table
  2518. * @param {boolean} [withoutDbName=false] Indicates wheather table name should contain the database name
  2519. * @return {String|Db.Expression} The table name as string optionally without database name if no table sharding was started
  2520. * or Db.Expression object with prefix and database name templates is table was sharded
  2521. */
  2522. Base.table = function (withoutDbName) {
  2523. if (Q.Config.get(['Db', 'connections', '$connectionName', 'indexes', '$class_name_base'], false)) {
  2524. return new Db.Expression((withoutDbName ? '' : '{\$dbname}.')+'{\$prefix}$table_name_base');
  2525. } else {
  2526. var conn = Db.getConnection('$connectionName');
  2527. var prefix = conn.prefix || '';
  2528. var tableName = prefix + '$table_name_base';
  2529. var dbname = Base.table.dbname;
  2530. if (!dbname) {
  2531. var dsn = Db.parseDsnString(conn['dsn']);
  2532. dbname = Base.table.dbname = dsn.dbname;
  2533. }
  2534. return withoutDbName ? tableName : dbname + '.' + tableName;
  2535. }
  2536. };
  2537.  
  2538. $dc
  2539. * The connection name for the class
  2540. * @method connectionName
  2541. * @return {String} The name of the connection
  2542. */
  2543. Base.connectionName = function() {
  2544. return '$connectionName';
  2545. };
  2546.  
  2547. $dc
  2548. * Create SELECT query to the class table
  2549. * @method SELECT
  2550. * @param {String|Object} [fields=null] The fields as strings, or object of {alias:field} pairs.
  2551. * The default is to return all fields of the table.
  2552. * @param {String|Object} [alias=null] The tables as strings, or object of {alias:table} pairs.
  2553. * @return {Db.Query.Mysql} The generated query
  2554. */
  2555. Base.SELECT = function(fields, alias) {
  2556. if (!fields) {
  2557. fields = Base.fieldNames().map(function (fn) {
  2558. return fn;
  2559. }).join(',');
  2560. }
  2561. var q = Base.db().SELECT(fields, Base.table()+(alias ? ' '+alias : ''));
  2562. q.className = '$class_name';
  2563. return q;
  2564. };
  2565.  
  2566. $dc
  2567. * Create UPDATE query to the class table. Use Db.Query.Mysql.set() method to define SET clause
  2568. * @method UPDATE
  2569. * @param {String} [alias=null] Table alias
  2570. * @return {Db.Query.Mysql} The generated query
  2571. */
  2572. Base.UPDATE = function(alias) {
  2573. var q = Base.db().UPDATE(Base.table()+(alias ? ' '+alias : ''));
  2574. q.className = '$class_name';
  2575. return q;
  2576. };
  2577.  
  2578. $dc
  2579. * Create DELETE query to the class table
  2580. * @method DELETE
  2581. * @param {Object}[table_using=null] If set, adds a USING clause with this table
  2582. * @param {String} [alias=null] Table alias
  2583. * @return {Db.Query.Mysql} The generated query
  2584. */
  2585. Base.DELETE = function(table_using, alias) {
  2586. var q = Base.db().DELETE(Base.table()+(alias ? ' '+alias : ''), table_using);
  2587. q.className = '$class_name';
  2588. return q;
  2589. };
  2590.  
  2591. $dc
  2592. * Create INSERT query to the class table
  2593. * @method INSERT
  2594. * @param {Object} [fields={}] The fields as an associative array of {column: value} pairs
  2595. * @param {String} [alias=null] Table alias
  2596. * @return {Db.Query.Mysql} The generated query
  2597. */
  2598. Base.INSERT = function(fields, alias) {
  2599. var q = Base.db().INSERT(Base.table()+(alias ? ' '+alias : ''), fields || {});
  2600. q.className = '$class_name';
  2601. return q;
  2602. };
  2603.  
  2604. $dc
  2605. * Create raw query with BEGIN clause.
  2606. * You'll have to specify shards yourself when calling execute().
  2607. * @method BEGIN
  2608. * @param {string} [\$lockType] First parameter to pass to query.begin() function
  2609. * @return {Db.Query.Mysql} The generated query
  2610. */
  2611. Base.BEGIN = function(\$lockType) {
  2612. var q = Base.db().rawQuery('').begin(\$lockType);
  2613. q.className = '$class_name';
  2614. return q;
  2615. };
  2616.  
  2617. $dc
  2618. * Create raw query with COMMIT clause
  2619. * You'll have to specify shards yourself when calling execute().
  2620. * @method COMMIT
  2621. * @return {Db.Query.Mysql} The generated query
  2622. */
  2623. Base.COMMIT = function() {
  2624. var q = Base.db().rawQuery('').commit();
  2625. q.className = '$class_name';
  2626. return q;
  2627. };
  2628.  
  2629. $dc
  2630. * Create raw query with ROLLBACK clause
  2631. * @method ROLLBACK
  2632. * @param {Object} criteria can be used to target the query to some shards.
  2633. * Otherwise you'll have to specify shards yourself when calling execute().
  2634. * @return {Db.Query.Mysql} The generated query
  2635. */
  2636. Base.ROLLBACK = function(criteria) {
  2637. var q = Base.db().rawQuery('').rollback(crieria);
  2638. q.className = '$class_name';
  2639. return q;
  2640. };
  2641.  
  2642. $dc
  2643. * The name of the class
  2644. * @property className
  2645. * @type string
  2646. */
  2647. Base.prototype.className = "$class_name";
  2648.  
  2649. // Instance methods
  2650.  
  2651. $dc
  2652. * Create INSERT query to the class table
  2653. * @method INSERT
  2654. * @param {object} [fields={}] The fields as an associative array of {column: value} pairs
  2655. * @param {string} [alias=null] Table alias
  2656. * @return {Db.Query.Mysql} The generated query
  2657. */
  2658. Base.prototype.setUp = function() {
  2659. // does nothing for now
  2660. };
  2661.  
  2662. $dc
  2663. * Create INSERT query to the class table
  2664. * @method INSERT
  2665. * @param {object} [fields={}] The fields as an associative array of {column: value} pairs
  2666. * @param {string} [alias=null] Table alias
  2667. * @return {Db.Query.Mysql} The generated query
  2668. */
  2669. Base.prototype.db = function () {
  2670. return Base.db();
  2671. };
  2672.  
  2673. $dc
  2674. * Retrieve the table name to use in SQL statements
  2675. * @method table
  2676. * @param {boolean} [withoutDbName=false] Indicates wheather table name should contain the database name
  2677. * @return {String|Db.Expression} The table name as string optionally without database name if no table sharding was started
  2678. * or Db.Expression object with prefix and database name templates is table was sharded
  2679. */
  2680. Base.prototype.table = function () {
  2681. return Base.table();
  2682. };
  2683.  
  2684. $dc
  2685. * Retrieves primary key fields names for class table
  2686. * @method primaryKey
  2687. * @return {string[]} An array of field names
  2688. */
  2689. Base.prototype.primaryKey = function () {
  2690. return $pk_json_indented;
  2691. };
  2692.  
  2693. $dc
  2694. * Retrieves field names for class table
  2695. * @method fieldNames
  2696. * @return {array} An array of field names
  2697. */
  2698. Base.prototype.fieldNames = function () {
  2699. return Base.fieldNames();
  2700. };
  2701.  
  2702. $dc
  2703. * Retrieves field names for class table
  2704. * @method fieldNames
  2705. * @static
  2706. * @return {array} An array of field names
  2707. */
  2708. Base.fieldNames = function () {
  2709. return $field_names_json_indented;
  2710. };
  2711.  
  2712. $js_functions_string
  2713.  
  2714. module.exports = Base;
  2715. EOT;
  2716.  
  2717. // Return the base class
  2718. return $base_class_string; // unless the above line threw an exception
  2719. }
  2720. }
  2721.