Created
April 23, 2013 22:58
-
-
Save EGreg/5448157 to your computer and use it in GitHub Desktop.
testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @module Db | |
*/ | |
class Db_Mysql implements iDb | |
{ | |
/** | |
* This class lets you create and use PDO database connections. | |
* @class Db_Mysql | |
* @extends iDb | |
* @constructor | |
* | |
* @param {string} $conn_name The name of the connection out of the connections added with Db::setConnection() | |
* This is required for actually connecting to the database. | |
* @param {PDO} [$pdo=null] Existing PDO connection. Only accepts connections to MySQL. | |
*/ | |
function __construct ($conn_name, PDO $pdo = null) | |
{ | |
$this->conn_name = $conn_name; | |
if ($pdo) { | |
// The following statement may throw an exception, which is fine. | |
$driver_name = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); | |
if (strtolower($driver_name) != 'mysql') | |
throw new Exception("the PDO object is not for mysql", -1); | |
$this->pdo = $pdo; | |
} | |
} | |
/** | |
* The PDO connection that this object uses | |
* @property $pdo | |
* @type PDO | |
*/ | |
public $pdo; | |
/** | |
* The array of all pdo objects that have been constructed | |
* @property $pdo_array | |
* @type array | |
* @protected | |
* @default array() | |
*/ | |
protected static $pdo_array = array(); | |
/** | |
* The name of the connection | |
* @property $conn_name | |
* @type string | |
* @protected | |
*/ | |
protected $conn_name; | |
/** | |
* The name of the shard currently selected with reallyConnect, if any | |
* @property $shard_name | |
* @type string | |
* @protected | |
*/ | |
protected $shard_name; | |
/** | |
* The database name of the shard currently selected with reallyConnect, if any | |
* @property $dbname | |
* @type string | |
*/ | |
public $dbname; | |
/** | |
* The prefix of the shard currently selected with reallyConnect, if any | |
* @property $prefix | |
* @type string | |
*/ | |
public $prefix; | |
/** | |
* Actually makes a connection to the database (by creating a PDO instance) | |
* @method reallyConnect | |
* @param {array} [$shard_name=null] A shard name that was added using Db::setShard. | |
* This modifies how we connect to the database. | |
* @return {PDO} The PDO object for connection | |
*/ | |
function reallyConnect($shard_name = null) | |
{ | |
$conn_name = $this->conn_name; | |
$conn_info = Db::getConnection($conn_name); | |
if (empty($conn_info)) { | |
throw new Exception("database connection \"$conn_name\" wasn't registered with Db.", -1); | |
} | |
if (empty($shard_name)) { | |
$shard_name = ''; | |
} | |
$modifications = Db::getShard($conn_name, $shard_name); | |
if (!isset($modifications)) { | |
$modifications = array(); | |
} | |
if (class_exists('Q')) { | |
/** | |
* @event Db/reallyConnect {before} | |
* @param {Db_Mysql} 'db' | |
* @param {string} 'shard_name' | |
* @param {array} 'modifications' | |
* @return {array} | |
* Extra modifications | |
*/ | |
$more = Q::event('Db/reallyConnect', array( | |
'db' => $this, | |
'shard_name' => $shard_name, | |
'modifications' => $modifications | |
), 'before'); | |
if ($more) { | |
$modifications = array_merge($modifications, $more); | |
} | |
} | |
$dsn = isset($modifications['dsn']) ? $modifications['dsn'] : $conn_info['dsn']; | |
$prefix = isset($modifications['prefix']) ? $modifications['prefix'] : $conn_info['prefix']; | |
$username = isset($modifications['username']) ? $modifications['username'] : $conn_info['username']; | |
$password = isset($modifications['password']) ? $modifications['password'] : $conn_info['password']; | |
$driver_options = isset($modifications['driver_options']) | |
? $modifications['driver_options'] | |
: isset($conn_info['driver_options']) ? $conn_info['driver_options'] : null; | |
// More dsn changes | |
$dsn_fields = array(); | |
foreach (array('host', 'port', 'dbname', 'unix_socket', 'charset') as $f) { | |
if (isset($modifications[$f])) { | |
$dsn_fields[$f] = $modifications[$f]; | |
} | |
} | |
if ($dsn_fields) { | |
$dsn_array = array_merge(Db::parseDsnString($dsn), $dsn_fields); | |
$dsn = 'mysql:'.http_build_query($dsn_array, '', ';'); | |
} else $dsn_array = Db::parseDsnString($dsn); | |
// The connection may have already been made with these parameters, | |
// in which case we will just retrieve the existing connection. | |
$this->pdo = self::pdo($dsn, $username, $password, $driver_options); | |
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
$this->shard_name = $shard_name; | |
$this->dbname = $dsn_array['dbname']; | |
$this->prefix = $prefix; | |
if (class_exists('Q')) { | |
/** | |
* @event Db/reallyConnect {after} | |
* @param {Db_Mysql} 'db' | |
* @param {string} 'shard_name' | |
* @param {array} 'modifications' | |
*/ | |
Q::event('Db/reallyConnect', array( | |
'db' => $this, | |
'shard_name' => $shard_name, | |
'modifications' => $modifications | |
), 'after'); | |
} | |
return $this->pdo; | |
} | |
/** | |
* Returns the name of the shard currently selected with reallyConnect, if any | |
* @method shardName | |
* @return {string} | |
*/ | |
function shardName() | |
{ | |
return $this->shard_name; | |
} | |
/** | |
* Gets the key into the associative $pdo_array | |
* corresponding to some database credentials. | |
* @method pdo | |
* @protected | |
* @static | |
* @param {string} $dsn The dsn to create PDO | |
* @param {string} $username Username for connection | |
* @param {string} $password Passwork for connection | |
* @param {array} $driver_options Driver options | |
* @return {PDO} | |
*/ | |
protected static function pdo ($dsn, $username, $password, $driver_options) | |
{ | |
$key = $dsn . $username . $password . serialize($driver_options); | |
if (isset(self::$pdo_array[$key])) { | |
return self::$pdo_array[$key]; | |
} | |
// Make a new connection to a database! | |
try { | |
self::$pdo_array[$key] = @new PDO($dsn, $username, $password, $driver_options); | |
} catch (Exception $e) { | |
if (class_exists('Q_Config') and Q_Config::get('Db', 'exceptions', 'log', true)) { | |
Q::log($e); | |
} | |
throw $e; | |
} | |
return self::$pdo_array[$key]; | |
} | |
/** | |
* Forwards all other calls to the PDO object | |
* @method __call | |
* @param {string} $name The function name | |
* @param {array} $arguments The arguments | |
* @return {mixed} The result of method call | |
*/ | |
function __call ($name, array $arguments) | |
{ | |
$this->reallyConnect(); | |
if (!is_callable(array($this->pdo, $name))) { | |
throw new Exception("neither Db_Mysql nor PDO supports the $name function"); | |
} | |
return call_user_func_array(array($this->pdo, $name), $arguments); | |
} | |
/** | |
* Returns the name of the connection with which this Db object was created. | |
* @method connectionName | |
* @return {string} | |
*/ | |
function connectionName () | |
{ | |
return isset($this->conn_name) ? $this->conn_name : null; | |
} | |
/** | |
* Returns the connection info with which this Db object was created. | |
* @method connection | |
* @return {string} | |
*/ | |
function connection() | |
{ | |
if (isset($this->conn_name)) { | |
return Db::getConnection($this->conn_name); | |
} | |
return null; | |
} | |
/** | |
* Returns an associative array representing the dsn | |
* @method dsn | |
* @return {array} | |
*/ | |
function dsn() | |
{ | |
$conn_info = Db::getConnection($this->conn_name); | |
if (empty($conn_info['dsn'])) { | |
throw new Exception( | |
'No dsn string found for the connection ' | |
. $this->conn_name | |
); | |
} | |
return Db::parseDsnString($conn_info['dsn']); | |
} | |
/** | |
* Returns the name of the database used | |
* @method dbName | |
* @return {string} | |
*/ | |
function dbName() | |
{ | |
$dsn = $this->dsn(); | |
if (empty($dsn)) | |
return null; | |
return $dsn['dbname']; | |
} | |
/** | |
* Creates a query to select fields from a table. Needs to be used with Db_Query::from(). | |
* @method select | |
* @param {string|array} $fields The fields as strings, or array of alias=>field | |
* @param {string|array} $tablesThe tables as strings, or array of alias=>table | |
* @return {Db_Query_Mysql} The resulting Db_Query object | |
*/ | |
function select ($fields, $tables = '') | |
{ | |
if (empty($fields)) | |
throw new Exception("fields not specified in call to 'select'."); | |
if (!isset($tables)) | |
throw new Exception("tables not specified in call to 'select'."); | |
$query = new Db_Query_Mysql($this, Db_Query::TYPE_SELECT); | |
return $query->select($fields, $tables); | |
} | |
/** | |
* Creates a query to insert a record into a table | |
* @method insert | |
* @param {string} $table_into The name of the table to insert into | |
* @param {array} $fields=array() The fields as an array of column=>value pairs | |
* @return {Db_Query_Mysql} The resulting Db_Query_Mysql object | |
*/ | |
function insert ($table_into, array $fields = array()) | |
{ | |
if (empty($table_into)) | |
throw new Exception("table not specified in call to 'insert'."); | |
// $fields might be an empty array, | |
// but the insert will still be attempted. | |
$columns_list = array(); | |
$values_list = array(); | |
foreach ($fields as $column => $value) { | |
$columns_list[] = "$column"; | |
if ($value instanceof Db_Expression) { | |
$values_list[] = "$value"; | |
} else { | |
$values_list[] = ":$column"; | |
} | |
} | |
$columns_string = implode(', ', $columns_list); | |
$values_string = implode(', ', $values_list); | |
$clauses = array( | |
'INTO' => "$table_into ($columns_string)", 'VALUES' => $values_string | |
); | |
return new Db_Query_Mysql($this, Db_Query::TYPE_INSERT, $clauses, $fields, $table_into); | |
} | |
/** | |
* Inserts multiple records into a single table, preparing the statement only once, | |
* and executes all the queries. | |
* @method insertManyAndExecute | |
* @param {string} $table_into The name of the table to insert into | |
* @param {array} [$records=array()] The array of records to insert. | |
* (The field names for the prepared statement are taken from the first record.) | |
* You cannot use Db_Expression objects here, because the function binds all parameters with PDO. | |
* @param {array} [$options=array()] | |
* An associative array of options, including: | |
* | |
* * "chunkSize" => The number of rows to insert at a time. Defaults to 20. | |
* You can also put 0 here, which means unlimited chunks, but it's not recommended. | |
* * "onDuplicateKeyUpdate" => You can put an array of fieldname => value pairs here, | |
* which will add an ON DUPLICATE KEY UPDATE clause to the query. | |
*/ | |
function insertManyAndExecute ($table_into, array $records = array(), $options = array()) | |
{ | |
if (empty($table_into)) | |
throw new Exception("table not specified in call to 'insertManyAndExecute'."); | |
if (empty($records)) | |
return false; | |
$chunkSize = 20; | |
$onDuplicateKeyUpdate = null; | |
extract($options); | |
if ($chunkSize < 0) | |
return false; | |
// Get the columns list | |
foreach ($records[0] as $column => $value) { | |
$columns_list[] = "$column"; | |
} | |
$columns_string = implode(', ', $columns_list); | |
$into = "$table_into ($columns_string)"; | |
$index = 1; | |
$first_chunk = true; | |
$to_bind = array(); | |
$record_count = count($records); | |
$update_fields = array(); | |
$odku_clause = ''; | |
if ($onDuplicateKeyUpdate) { | |
$odku_clause = "\n\t ON DUPLICATE KEY UPDATE "; | |
$parts = array(); | |
foreach ($onDuplicateKeyUpdate as $k => $v) { | |
if ($v instanceof Db_Expression) { | |
$parts[] .= "`$k` = $v"; | |
} else { | |
$parts[] .= "`$k` = :__update_$k"; | |
$update_fields["__update_$k"] = $v; | |
} | |
} | |
$odku_clause .= implode(",\n\t", $parts); | |
} | |
// Execute all the queries using this prepared statement | |
$row_of_chunk = 1; | |
foreach ($records as $record) { | |
if ($first_chunk) { | |
// Prepare statement from first query | |
$values_list = array(); | |
foreach ($record as $column => $value) { | |
if ($value instanceof Db_Expression) { | |
$values_list[] = "$value"; | |
} else { | |
$values_list[] = ":$column" . $row_of_chunk; | |
} | |
} | |
$values_string = implode(', ', $values_list); | |
if ($index == 1) { | |
$q = "INSERT INTO $into VALUES ($values_string) "; | |
} else { | |
$q .= ",\n\t ($values_string) "; | |
} | |
} | |
foreach ($record as $column => $value) { | |
$to_bind[$column . $row_of_chunk] = $value; | |
} | |
++$row_of_chunk; | |
if ($chunkSize === 1 | |
or ($chunkSize > 1 and $row_of_chunk % $chunkSize === 1) | |
or $index === $record_count) { | |
if ($onDuplicateKeyUpdate) { | |
$q .= $odku_clause; | |
} | |
$q .= ';'; | |
$first_chunk = false; | |
$this->rawQuery($q)->bind($to_bind)->bind($update_fields) | |
->execute(true); | |
$row_of_chunk = 1; | |
} | |
++$index; | |
} | |
} | |
/** | |
* Creates a query to update records. Needs to be used with {@link Db_Query::set} | |
* @method update | |
* @param {string} $table The table to update | |
* @return {Db_Query_Mysql} The resulting Db_Query object | |
*/ | |
function update ($table) | |
{ | |
if (empty($table)) | |
throw new Exception("table not specified in call to 'update'."); | |
$clauses = array('UPDATE' => "$table"); | |
return new Db_Query_Mysql($this, Db_Query::TYPE_UPDATE, $clauses, array(), $table); | |
} | |
/** | |
* Creates a query to delete records. | |
* @method delete | |
* @param {string} $table_from The table to delete from | |
* @param {string} [$table_using=null] If set, adds a USING clause with this table. You can then use ->join() with the resulting Db_Query. | |
* @return {Db_Query_Mysql} | |
*/ | |
function delete ($table_from, $table_using = null) | |
{ | |
if (empty($table_from)) | |
throw new Exception("table not specified in call to 'delete'."); | |
if (isset($table_using) and !is_string($table_using)) { | |
throw new Exception("table_using field must be a string"); | |
} | |
if (isset($table_using)) | |
$clauses = array('FROM' => "$table_from USING $table_using"); | |
else | |
$clauses = array('FROM' => "$table_from"); | |
return new Db_Query_Mysql($this, Db_Query::TYPE_DELETE, $clauses, array(), $table_from); | |
} | |
/** | |
* Creates a query from raw SQL | |
* @method rawQuery | |
* @param {string} $sql May contain more than one SQL statement | |
* @param {array} [$bind=array()] An array of parameters to bind to the query, using | |
* the Db_Query_Mysql->bind method. | |
* @return {Db_Query_Mysql} | |
*/ | |
function rawQuery ($sql, $bind = array()) | |
{ | |
$clauses = array('RAW' => $sql); | |
$query = new Db_Query_Mysql($this, Db_Query::TYPE_RAW, $clauses); | |
if ($bind) { | |
$query->bind($bind); | |
} | |
return $query; | |
} | |
/** | |
* Creates a query to rollback a previously started transaction. | |
* @method update | |
* @param {array} $criteria The criteria to use, for sharding | |
* @return {Db_Query_Mysql} The resulting Db_Query object | |
*/ | |
function rollback ($criteria = null) | |
{ | |
$query = new Db_Query_Mysql($this, Db_Query::TYPE_ROLLBACK, array('ROLLBACK' => true)); | |
$query->rollback($criteria); | |
return $query; | |
} | |
/** | |
* Sorts a table in chunks | |
* @method rank | |
* @param {string} $table The name of the table in the database | |
* @param {string} $pts_field The name of the field to rank by. | |
* @param {string} $rank_field The rank field to update in all the rows | |
* @param {integer} [$chunk_size=1000] The number of rows to process at a time. Default is 1000. | |
* This is so the queries don't tie up the database server for very long, | |
* letting it service website requests and other things. | |
* @param {integer} [$rank_level2=0] Since the ranking is done in chunks, the function must know | |
* which rows have not been processed yet. If this field is empty (default) | |
* then the function sets the rank_field to 0 in all the rows, before | |
* starting the ranking process. | |
* (That might be a time consuming operation.) | |
* Otherwise, if $rank is a nonzero integer, then the function alternates | |
* between the ranges | |
* 0 to $rank_level2, and $rank_level2 to $rank_level2 * 2. | |
* That is, after it is finished, all the ratings will be in one of these | |
* two ranges. | |
* If not empty, this should be a very large number, like a billion. | |
* @param {string} [$order_by_clause=null] The order clause to use when calculating ranks. | |
* Default is "ORDER BY `$pts_field` DESC" | |
*/ | |
function rank( | |
$table, | |
$pts_field, | |
$rank_field, | |
$chunk_size = 1000, | |
$rank_level2 = 0, | |
$order_by_clause = null) | |
{ | |
if (!isset($order_by_clause)) | |
$order_by_clause = "ORDER BY $pts_field DESC"; | |
if (empty($rank_level2)) { | |
$this->update($table) | |
->set(array($rank_field => 0)) | |
->execute(); | |
$rank_base = 0; | |
$condition = "$rank_field = 0 OR $rank_field IS NULL"; | |
} else { | |
$rows = $this->select($pts_field, $table) | |
->where("$rank_field < $rank_level2") | |
->limit(1) | |
->fetchAll(); | |
if (!empty($rows)) { | |
// There are no ranks above $rank_level2. Create ranks on level 2. | |
$rank_base = $rank_level2; | |
$condition = "$rank_field < $rank_level2"; | |
} else { | |
// The ranks are all above $rank_level2. Create ranks on level 1. | |
$rank_base = 0; | |
$condition = "$rank_field >= $rank_level2"; | |
} | |
} | |
// Count all the rows | |
$rows = $this->rawQuery("SELECT COUNT(1) _count FROM $table")->fetchAll(PDO::FETCH_ASSOC); | |
$count = $rows[0]['_count']; | |
// Here comes the magic: | |
$offset = 0; | |
$this->rawQuery("set @rank = $offset")->execute(); | |
do { | |
$this->rawQuery(" | |
UPDATE $table | |
SET $rank_field = $rank_base + (@rank := @rank + 1) | |
WHERE $condition | |
$order_by_clause | |
LIMIT $chunk_size | |
")->execute(); | |
$offset += $chunk_size; | |
} while ($count-$offset > 0); | |
} | |
/** | |
* Generate an ID that is unique in a table | |
* @method uniqueId | |
* @param {string} $table The name of the table | |
* @param {string} $field The name of the field to check for uniqueness. | |
* You should probably have an index starting with this field. | |
* @param {array} [$where=array()] You can indicate conditions here to limit the search for | |
* an existing value. The result is an id that is unique within a certain partition. | |
* @param {array} [$options=array()] | |
* Optional array used to override default options: | |
* | |
* * "length" => Defaults to 7. The length of the ID to generate, after the prefix. | |
* * "characters" => A string of characters from which to construct the ID. | |
* * "prefix" => The prefix to prepend to the unique id. Defaults to ''. | |
* * "filter" => The name of a function that will take the generated string and | |
* check it. The filter function can modify the string by returning another string, | |
* or simply reject the string by returning false, in which another string will be generated. | |
*/ | |
function uniqueId( | |
$table, | |
$field, | |
$where = array(), | |
$options = array()) | |
{ | |
$length = 7; | |
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'; | |
$prefix = ''; | |
extract($options); | |
$count = strlen($characters); | |
do { | |
$id = $prefix; | |
for ($i=0; $i<$length; ++$i) { | |
$id .= $characters[mt_rand(0, $count-1)]; | |
} | |
if (!empty($options['filter'])) { | |
$ret = Q::call($options['filter'], array(compact('id', 'table', 'field', 'where', 'options'))); | |
if ($ret === false) { | |
continue; | |
} else if ($ret) { | |
$id = $ret; | |
} | |
} | |
$q = $this->select($field, $table) | |
->where(array($field => $id)); | |
if ($where) { | |
$q->andWhere($where); | |
} | |
$rows = $q->limit(1)->fetchAll(); | |
} while ($rows); | |
return $id; | |
} | |
/** | |
* Returns a timestamp from a Date string | |
* @method fromDate | |
* @param {string} $datetime The Date string that comes from the db | |
* @return {integer} The timestamp | |
*/ | |
function fromDate ($date) | |
{ | |
$year = substr($date, 0, 4); | |
$month = substr($date, 5, 2); | |
$day = substr($date, 8, 2); | |
return mktime(0, 0, 0, $month, $day, $year); | |
} | |
/** | |
* Returns a timestamp from a DateTime string | |
* @method fromDateTime | |
* @param {string} $syntax The format of the date string, see `date()` function. | |
* @param {string} $datetime The DateTime string that comes from the db | |
* @return {integer} The timestamp | |
*/ | |
function fromDateTime ($datetime) | |
{ | |
$year = substr($datetime, 0, 4); | |
$month = substr($datetime, 5, 2); | |
$day = substr($datetime, 8, 2); | |
$hour = substr($datetime, 11, 2); | |
$min = substr($datetime, 14, 2); | |
$sec = substr($datetime, 17, 2); | |
return mktime($hour, $min, $sec, $month, $day, $year); | |
} | |
/** | |
* Returns a Date string to store in the database | |
* @method toDate | |
* @param {string} $timestamp The UNIX timestamp, e.g. from a strtotime function | |
* @return {string} | |
*/ | |
function toDate ($timestamp) | |
{ | |
return date('Y-m-d', $timestamp); | |
} | |
/** | |
* Returns a DateTime string to store in the database | |
* @method toDateTime | |
* @param {string} $timestamp The UNIX timestamp, e.g. from a strtotime function | |
* @return {string} | |
*/ | |
function toDateTime ($timestamp) | |
{ | |
return date('Y-m-d H:i:s', $timestamp); | |
} | |
/** | |
* Takes a MySQL script and returns an array of queries. | |
* When DELIMITER is changed, respects that too. | |
* @method scriptToQueries | |
* @param {string} $script The text of the script | |
* @param {callable} [$callback=null] Optional callback to call for each query. | |
* @return {array} An array of the SQL queries. | |
*/ | |
function scriptToQueries($script, $callback = null) | |
{ | |
$this->reallyConnect(); | |
$version_string = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION); | |
$version_parts = explode('.', $version_string); | |
sprintf("%1d%02d%02d", $version_parts[0], $version_parts[1], $version_parts[2]); | |
$script_stripped = $script; | |
return $this->scriptToQueries_internal($script_stripped, $callback); | |
} | |
/** | |
* Takes stripped MySQL script and returns an array of queries. | |
* When DELIMITER is changed, respects that too. | |
* @method scriptToQueries_internal | |
* @protected | |
* @param {string} $script The text of the script | |
* @param {callable} [$callback=null] Optional callback to call for each query. | |
* @return {array} An array of the SQL queries. | |
*/ | |
protected function scriptToQueries_internal($script, $callback = null) | |
{ | |
$queries = array(); | |
$script_len = strlen($script); | |
$this->reallyConnect(); | |
$version_string = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION); | |
$version_parts = explode('.', $version_string); | |
$version = sprintf("%1d%02d%02d", $version_parts[0], $version_parts[1], $version_parts[2]); | |
//$mode_n = 0; // normal | |
$mode_c = 1; // comments | |
$mode_sq = 2; // single quotes | |
$mode_dq = 3; // double quotes | |
$mode_bt = 4; // backticks | |
$mode_lc = 5; // line comment (hash or double-dash) | |
$mode_ds = 6; // delimiter statement | |
$cur_pos = 0; | |
$d = ';'; // delimiter | |
$d_len = strlen($d); | |
$query_start_pos = 0; | |
$del_start_pos_array = array(); | |
$del_end_pos_array = array(); | |
if (class_exists('Q_Config')) { | |
$separator = Q_Config::expect('Db', 'sql', 'querySeparator'); | |
} else { | |
$separator = "\n-------- NEXT QUERY STARTS HERE --------\n"; | |
} | |
$found = strpos($script, $separator); | |
if ($found !== false) { | |
// This script was specially crafted for quick parsing | |
$queries = explode($separator, $script); | |
foreach ($queries as $i => $query) { | |
if (!trim($query)) { | |
unset($queries[$i]); | |
} | |
} | |
return $queries; | |
} | |
while (1) { | |
$c_pos = strpos($script, "/*", $cur_pos); | |
$sq_pos = strpos($script, "'", $cur_pos); | |
$dq_pos = strpos($script, "\"", $cur_pos); | |
$bt_pos = strpos($script, "`", $cur_pos); | |
$c2_pos = strpos($script, "--", $cur_pos); | |
$c3_pos = strpos($script, "#", $cur_pos); | |
$ds_pos = stripos($script, "\nDELIMITER ", $cur_pos); | |
$next_pos = false; | |
if ($c_pos !== false) { | |
$next_mode = $mode_c; | |
$next_pos = $c_pos; | |
$next_end_str = "*/"; | |
$next_end_str_len = 2; | |
} | |
if ($sq_pos !== false and ($next_pos === false or $sq_pos < $next_pos)) { | |
$next_mode = $mode_sq; | |
$next_pos = $sq_pos; | |
$next_end_str = "'"; | |
$next_end_str_len = 1; | |
} | |
if ($dq_pos !== false and ($next_pos === false or $dq_pos < $next_pos)) { | |
$next_mode = $mode_dq; | |
$next_pos = $dq_pos; | |
$next_end_str = "\""; | |
$next_end_str_len = 1; | |
} | |
if ($bt_pos !== false and ($next_pos === false or $bt_pos < $next_pos)) { | |
$next_mode = $mode_bt; | |
$next_pos = $bt_pos; | |
$next_end_str = "`"; | |
$next_end_str_len = 1; | |
} | |
if ($c2_pos !== false and ($next_pos === false or $c2_pos < $next_pos) | |
and ($script[$c2_pos+2] == " " or $script[$c2_pos+2] == "\t")) { | |
$next_mode = $mode_lc; | |
$next_pos = $c2_pos; | |
$next_end_str = "\n"; | |
$next_end_str_len = 1; | |
} | |
if ($c3_pos !== false and ($next_pos === false or $c3_pos < $next_pos)) { | |
$next_mode = $mode_lc; | |
$next_pos = $c3_pos; | |
$next_end_str = "\n"; | |
$next_end_str_len = 1; | |
} | |
if ($ds_pos !== false and ($next_pos === false or $ds_pos < $next_pos)) { | |
$next_mode = $mode_ds; | |
$next_pos = $ds_pos; | |
$next_end_str = "\n"; | |
$next_end_str_len = 1; | |
} | |
// If at this point, $next_pos === false, then | |
// we are in the final stretch. | |
// Until the end of the string, we have normal mode. | |
// Right now, we are in normal mode. | |
$d_pos = strpos($script, $d, $cur_pos); | |
while ($d_pos !== false and ($next_pos === false or $d_pos < $next_pos)) { | |
$query = substr($script, $query_start_pos, $d_pos - $query_start_pos); | |
// remove parts of the query string based on the "del_" arrays | |
$del_pos_count = count($del_start_pos_array); | |
if ($del_pos_count == 0) { | |
$query2 = $query; | |
} else { | |
$query2 = substr($query, 0, $del_start_pos_array[0] - $query_start_pos); | |
for ($i=1; $i < $del_pos_count; ++$i) { | |
$query2 .= substr($query, $del_end_pos_array[$i-1] - $query_start_pos, | |
$del_start_pos_array[$i] - $del_end_pos_array[$i-1]); | |
} | |
$query2 .= substr($query, | |
$del_end_pos_array[$del_pos_count - 1] - $query_start_pos); | |
} | |
$del_start_pos_array = array(); // reset these arrays | |
$del_end_pos_array = array(); // reset these arrays | |
$query_start_pos = $d_pos + $d_len; | |
$cur_pos = $query_start_pos; | |
$query2 = trim($query2); | |
if ($query2) | |
$queries[] = $query2; // <----- here is where we add to the main array | |
if ($callback) { | |
call_user_func($callback, $query2); | |
} | |
$d_pos = strpos($script, $d, $cur_pos); | |
}; | |
if ($next_pos === false) { | |
// Add the last query and get out of here: | |
$query = substr($script, $query_start_pos); | |
// remove parts of the query string based on the "del_" arrays | |
$del_pos_count = count($del_start_pos_array); | |
if ($del_pos_count == 0) { | |
$query2 = $query; | |
} else { | |
$query2 = substr($query, 0, $del_start_pos_array[0] - $query_start_pos); | |
for ($i=1; $i < $del_pos_count; ++$i) { | |
$query2 .= substr($query, $del_end_pos_array[$i-1] - $query_start_pos, | |
$del_start_pos_array[$i] - $del_end_pos_array[$i-1]); | |
} | |
if ($del_end_pos_array[$del_pos_count - 1] !== false) { | |
$query2 .= substr($query, | |
$del_end_pos_array[$del_pos_count - 1] - $query_start_pos); | |
} | |
} | |
$query2 = trim($query2); | |
if ($query2) { | |
$queries[] = $query2; | |
if ($callback) { | |
call_user_func($callback, $query2); | |
} | |
} | |
break; | |
} | |
if ($next_mode == $mode_c) { | |
// We are inside a comment | |
$end_pos = strpos($script, $next_end_str, $next_pos + 1); | |
if ($end_pos === false) { | |
throw new Exception("unterminated comment -- missing terminating */ characters."); | |
} | |
$version_comment = false; | |
if ($script[$next_pos + 2] == '!') { | |
$ver = substr($script, $next_pos + 3, 5); | |
if ($version >= $ver) { | |
// we are in a version comment | |
$version_comment = true; | |
} | |
} | |
// Add to list of areas to ignore | |
if ($version_comment) { | |
$del_start_pos_array[] = $next_pos; | |
$del_end_pos_array[] = $next_pos + 3 + 5; | |
$del_start_pos_array[] = $end_pos; | |
$del_end_pos_array[] = $end_pos + $next_end_str_len; | |
} else { | |
$del_start_pos_array[] = $next_pos; | |
$del_end_pos_array[] = $end_pos + $next_end_str_len; | |
} | |
} else if ($next_mode == $mode_lc) { | |
// We are inside a line comment | |
$end_pos = strpos($script, $next_end_str, $next_pos + 1); | |
$del_start_pos_array[] = $next_pos; | |
if ($end_pos !== false) { | |
$del_end_pos_array[] = $end_pos + $next_end_str_len; | |
} else { | |
$del_end_pos_array[] = false; | |
} | |
} else if ($next_mode == $mode_ds) { | |
// We are inside a DELIMITER statement | |
$start_pos = $next_pos; | |
$end_pos = strpos($script, $next_end_str, $next_pos + 11); | |
$del_start_pos_array[] = $next_pos; | |
if ($end_pos !== false) { | |
$del_end_pos_array[] = $end_pos + $next_end_str_len; | |
} else { | |
// this is the last statement in the script, it seems. | |
// Might look funny, like: | |
// DELIMITER aa sfghjkhsgkjlfhdsgjkfdglhdfsgkjfhgjdlk | |
$del_end_pos_array[] = false; | |
} | |
// set a new delimiter! | |
$try_d = trim(substr($script, $ds_pos + 11, $end_pos - ($ds_pos + 11))); | |
if (!empty($try_d)) { | |
$d = $try_d; | |
$d_len = strlen($d); | |
} // otherwise malformed delimiter statement or end of file | |
} else { | |
// We are inside a string | |
$start_pos = $next_pos; | |
$try_end_pos = $next_pos; | |
do { | |
$end_pos = false; | |
$try_end_pos = strpos($script, $next_end_str, $try_end_pos + 1); | |
if ($try_end_pos === false) { | |
throw new Exception("unterminated string -- missing terminating $next_end_str character."); | |
} | |
if ($try_end_pos+1 >= $script_len) { | |
$end_pos = $try_end_pos; | |
break; | |
} | |
if ($script[$try_end_pos+1] == $next_end_str) { | |
++$try_end_pos; | |
continue; | |
} | |
$bs_count = 0; | |
for ($i = $try_end_pos - 1; $i > $next_pos; --$i) { | |
if ($script[$i] == "\\") { | |
++$bs_count; | |
} else { | |
break; | |
} | |
} | |
if ($bs_count % 2 == 0) { | |
$end_pos = $try_end_pos; | |
} | |
} while ($end_pos === false); | |
// If we are here, we have found the end of the string, | |
// and are back in normal mode. | |
} | |
// We have exited the previous mode and set end_pos. | |
if ($end_pos === false) | |
break; | |
$cur_pos = $end_pos + $next_end_str_len; | |
} | |
foreach ($queries as $i => $query) { | |
if ($query === false) { | |
unset($queries[$i]); | |
} | |
} | |
return $queries; | |
} | |
/** | |
* Generates base classes of the models, and if they don't exist, | |
* skeleton code for the models themselves. | |
* Use it only after you have made changes to the database schema. | |
* You shouldn't be using it on every request. | |
* @method generateModels | |
* @param {string} $directory The directory in which to generate the files. | |
* If the files already exist, they are not overwritten, | |
* unless they are inside the "Base" subdirectory. | |
* If the "Base" subdirectory does not exist, it is created. | |
* @param {string} [$classname_prefix=null] The prefix to prepend to the Base class names. | |
* If not specified, prefix becomes "Conn_Name_", where conn_name is the name of the connection. | |
* @throws {Exception} If the $connection is not registered, or the $directory | |
* does not exist, this function throws an exception. | |
*/ | |
function generateModels ( | |
$directory, | |
$classname_prefix = null) | |
{ | |
$dc = '/**'; | |
if (!file_exists($directory)) | |
throw new Exception("directory $directory does not exist."); | |
$conn_name = $this->connectionName(); | |
$conn = Db::getConnection($conn_name); | |
$prefix = empty($conn['prefix']) ? '' : $conn['prefix']; | |
$prefix_len = strlen($prefix); | |
if (!isset($classname_prefix)) { | |
$classname_prefix = isset($conn_name) ? $conn_name . '_' : ''; | |
} | |
$rows = $this->rawQuery('SHOW TABLES')->fetchAll(); | |
if (class_exists('Q_Config')) { | |
$ext = Q_Config::get('Q', 'extensions', 'class', 'php'); | |
} else { | |
$ext = 'php'; | |
} | |
$table_classnames = array(); | |
$js_table_classes_string = ''; | |
$class_name_prefix = rtrim(ucfirst($classname_prefix), "._"); | |
foreach ($rows as $row) { | |
uksort | |
$table_name = $row[0]; | |
$table_name_base = substr($table_name, $prefix_len); | |
$table_name_prefix = substr($table_name, 0, $prefix_len); | |
if (empty($table_name_base) or $table_name_prefix != $prefix or stristr($table_name, '_Q_') !== false) | |
continue; // no class generated | |
$class_name_base = null; | |
$js_base_class_string = ''; | |
$base_class_string = $this->codeForModelBaseClass( | |
$table_name, | |
$directory, | |
$classname_prefix, | |
$class_name_base, | |
null, | |
$js_base_class_string, | |
$table_comment | |
); // sets the $class_name variable | |
$class_name = ucfirst($classname_prefix) . $class_name_base; | |
if (empty($class_name)) | |
continue; // no class generated | |
$class_name_parts = explode('_', $class_name); | |
$class_filename = $directory.DS.implode(DS, $class_name_parts).'.php'; | |
$base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.php'; | |
$js_class_filename = $directory.DS.implode(DS, $class_name_parts).'.js'; | |
$js_base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.js'; | |
$js_base_class_require = 'Base'.DS.implode(DS, $class_name_parts); | |
$js_class_name = implode('.', $class_name_parts); | |
$js_base_class_name = implode('.Base.', $class_name_parts); | |
$class_extras = is_readable($class_filename.'.inc') ? file_get_contents($class_filename.'.inc') : ''; | |
$js_class_extras = is_readable($js_class_filename.'.inc') ? file_get_contents($js_class_filename.'.inc') : ''; | |
$class_string = <<<EOT | |
<?php | |
$dc | |
* @module $conn_name | |
*/ | |
$dc | |
* Class representing '$class_name_base' rows in the '$conn_name' database | |
* You can create an object of this class either to | |
* access its non-static methods, or to actually | |
* represent a $table_name_base row in the $conn_name database. | |
* | |
* @class $class_name | |
* @extends Base_$class_name | |
*/ | |
class $class_name extends Base_$class_name | |
{ | |
$dc | |
* The setUp() method is called the first time | |
* an object of this class is constructed. | |
* @method setUp | |
*/ | |
function setUp() | |
{ | |
parent::setUp(); | |
// INSERT YOUR CODE HERE | |
// e.g. \$this->hasMany(...) and stuff like that. | |
} | |
/* | |
* Add static methods here. | |
* If file '$class_name_base.php.inc' exists, its content is included | |
* * * */ | |
$class_extras | |
/* * * */ | |
$dc | |
* Implements the __set_state method, so it can work with | |
* with var_export and be re-imported successfully. | |
* @method __set_state | |
* @param {array} \$array | |
* @return {{$class_name}} Class instance | |
*/ | |
static function __set_state(array \$array) { | |
\$result = new $class_name(); | |
foreach(\$array as \$k => \$v) | |
\$result->\$k = \$v; | |
return \$result; | |
} | |
}; | |
EOT; | |
$js_class_string = <<<EOT | |
$dc | |
* Class representing $table_name_base rows. | |
* | |
* This description should be revised and expanded. | |
* | |
* @module $conn_name | |
*/ | |
var Q = require('Q'); | |
var Db = Q.require('Db'); | |
$dc | |
* Class representing '$class_name_base' rows in the '$conn_name' database | |
$table_comment * @namespace $class_name_prefix | |
* @class $class_name_base | |
* @extends Base.$js_class_name | |
* @constructor | |
* @param fields {object} The fields values to initialize table row as | |
* an associative array of `{column: value}` pairs | |
*/ | |
function $class_name (fields) { | |
$dc | |
* The setUp() method is called the first time | |
* an object of this class is constructed. | |
* @method setUp | |
*/ | |
this.setUp = function () { | |
// put any code here | |
}; | |
// Run constructors of mixed in objects | |
this.constructors.call(this, arguments); | |
/* | |
* Add any other methods to the model class by assigning them to this. | |
* If file '$class_name_base.js.inc' exists, its content is included | |
* * * */ | |
$js_class_extras | |
/* * * */ | |
} | |
Q.mixin($class_name, Q.require('$js_base_class_require')); | |
module.exports = $class_name; | |
EOT; | |
// overwrite base class file if necessary, but not the class file | |
Db_Utils::saveTextFile($base_class_filename, $base_class_string); | |
Db_Utils::saveTextFile($js_base_class_filename, $js_base_class_string); | |
if (! file_exists($class_filename)) { | |
Db_Utils::saveTextFile($class_filename, $class_string); | |
} | |
if (! file_exists($js_class_filename)) { | |
Db_Utils::saveTextFile($js_class_filename, $js_class_string); | |
} | |
$table_classnames[] = $class_name; | |
$js_table_classes_string .= <<<EOT | |
$dc | |
* Link to $conn_name.$class_name_base model | |
* @property $class_name_base | |
* @type $conn_name.$class_name_base | |
*/ | |
this.$class_name_base = Q.require('$conn_name/$class_name_base'); | |
EOT; | |
} | |
// Generate the "module model" base class file | |
$table_classnames_exported = var_export($table_classnames, true); | |
$table_classnames_json = $pk_json_indented = str_replace( | |
array("[", ",", "]"), | |
array("[\n\t\t", ",\n\t\t", "\n\t]"), | |
json_encode($table_classnames) | |
); | |
if (!empty($conn_name)) { | |
$class_name = Db::generateTableClassName($conn_name); | |
$class_name_parts = explode('_', $class_name); | |
$class_filename = $directory.DS.implode(DS, $class_name_parts).'.php'; | |
$base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.php'; | |
$js_class_filename = $directory.DS.implode(DS, $class_name_parts).'.js'; | |
$js_base_class_filename = $directory.DS.'Base'.DS.implode(DS, $class_name_parts).'.js'; | |
$js_base_class_require = 'Base'.DS.implode(DS, $class_name_parts); | |
// because table name can be {$prefix}_Q_plugin or {$prefix}_Q_app we need to know correct table name | |
$tables = $this->rawQuery("SHOW TABLES LIKE '{$prefix}Q_%'")->execute()->fetchAll(PDO::FETCH_NUM); | |
if ($tables) { | |
$tablename = $tables[0][0]; | |
$model_comment = $this->rawQuery("SELECT * FROM $tablename")->execute()->fetchAll(PDO::FETCH_NUM); | |
$model_comment = (isset($model_comment[0]) && !empty($model_comment[0][2])) ? " * <br/>{$model_comment[0][2]}\n" : ''; | |
} else { | |
$model_comment = ''; | |
} | |
$model_extras = is_readable($class_filename.'.inc') ? file_get_contents($class_filename.'.inc') : ''; | |
$js_model_extras = is_readable($js_class_filename.'.inc') ? file_get_contents($js_class_filename.'.inc') : ''; | |
$base_class_string = <<<EOT | |
<?php | |
$dc | |
* Autogenerated base class for the $conn_name model. | |
* | |
* Don't change this file, since it can be overwritten. | |
* Instead, change the $class_name.php file. | |
* | |
* @module $conn_name | |
*/ | |
$dc | |
* Base class for the $class_name model | |
* @class Base_$class_name | |
*/ | |
abstract class Base_$class_name | |
{ | |
$dc | |
* The list of model classes | |
* @property \$table_classnames | |
* @type array | |
*/ | |
static \$table_classnames = $table_classnames_exported; | |
$dc | |
* This method uses Db::connect() to establish a connection to database using information stored in the configuration. | |
* If the connection to Db object has already been made, it returns this Db object. | |
* @method db | |
* @return {iDb} The database object | |
*/ | |
static function db() | |
{ | |
return Db::connect('$conn_name'); | |
} | |
$dc | |
* The connection name for the class | |
* @method connectionName | |
* @return {string} The name of the connection | |
*/ | |
static function connectionName() | |
{ | |
return '$conn_name'; | |
} | |
}; | |
EOT; | |
$js_base_class_string = <<<EOT | |
$dc | |
* Autogenerated base class for the $conn_name model. | |
* | |
* Don't change this file, since it can be overwritten. | |
* Instead, change the $class_name.js file. | |
* | |
* @module $conn_name | |
*/ | |
var Q = require('Q'); | |
var Db = Q.require('Db'); | |
$dc | |
* Base class for the $class_name model | |
* @namespace Base | |
* @class $class_name | |
* @static | |
*/ | |
module.exports = function () { | |
$dc | |
* The list of model classes | |
* @property tableClasses | |
* @type array | |
*/ | |
this.tableClasses = $table_classnames_json; | |
$dc | |
* This method uses Db.connect() to establish a connection to database using information stored in the configuration. | |
* If the connection to Db object has already been made, it returns this Db object. | |
* @method db | |
* @return {Db} The database connection | |
*/ | |
this.db = function () { | |
return Db.connect('$conn_name'); | |
}; | |
$dc | |
* The connection name for the class | |
* @method connectionName | |
* @return {string} The name of the connection | |
*/ | |
this.connectionName = function() { | |
return '$conn_name'; | |
}; | |
$js_table_classes_string | |
return this; | |
}; | |
EOT; | |
$class_string = <<<EOT | |
<?php | |
$dc | |
* $class_name_prefix model | |
$model_comment * @module $conn_name | |
* @main $conn_name | |
*/ | |
$dc | |
* Static methods for the $conn_name models. | |
* @class $class_name | |
* @extends Base_$class_name | |
*/ | |
abstract class $class_name extends Base_$class_name | |
{ | |
/* | |
* This is where you would place all the static methods for the models, | |
* the ones that don't strongly pertain to a particular row or table. | |
* If file '$class_name.php.inc' exists, its content is included | |
* * * */ | |
$model_extras | |
/* * * */ | |
}; | |
EOT; | |
$js_class_string = <<<EOT | |
$dc | |
* $class_name_prefix model | |
$model_comment * @module $conn_name | |
* @main $conn_name | |
*/ | |
var Q = require('Q'); | |
$dc | |
* Static methods for the $class_name_prefix model | |
* @class $class_name_prefix | |
* @extends Base.$class_name_prefix | |
* @static | |
*/ | |
var $conn_name = module.exports; | |
Q.require('$js_base_class_require').apply($conn_name); | |
/* | |
* This is where you would place all the static methods for the models, | |
* the ones that don't strongly pertain to a particular row or table. | |
* Just assign them as methods of the $conn_name object. | |
* If file '$class_name.js.inc' exists, its content is included | |
* * * */ | |
$js_model_extras | |
/* * * */ | |
EOT; | |
// overwrite base class file if necessary, but not the class file | |
Db_Utils::saveTextFile($base_class_filename, $base_class_string); | |
Db_Utils::saveTextFile($js_base_class_filename, $js_base_class_string); | |
if (! file_exists($class_filename)) { | |
Db_Utils::saveTextFile($class_filename, $class_string); | |
} | |
if (! file_exists($js_class_filename)) { | |
Db_Utils::saveTextFile($js_class_filename, $js_class_string); | |
} | |
} | |
} | |
/** | |
* Generates code for a base class for the model | |
* @method codeForModelBaseClass | |
* @param {string} $table The name of the table to generate the code for. | |
* @param {string} $directory The path of the directory in which to place the model code. | |
* @param {string} [$classname_prefix=''] The prefix to prepend to the generated class names | |
* @param {&string} [$class_name_base=null] If set, this is the class name that is used. | |
* If an unset variable is passed, it is filled with the | |
* class name that is ultimately chosen, without the $classname_prefix | |
* @param {string} [$prefix=null] Defaults to the prefix of the tables, as specified in the connection. | |
* Pass null here to use the default, or a string to override it. | |
* @param {&string} [$js_code=null] The javascript code for the base class | |
* @param {&string} [$table_comment=''] The comment from the MySQL table if any | |
* @return {string} The generated code for the class. | |
*/ | |
function codeForModelBaseClass ( | |
$table_name, | |
$directory, | |
$classname_prefix = '', | |
&$class_name_base = null, | |
$prefix = null, | |
&$js_code = null, | |
&$table_comment = '') | |
{ | |
$dc = '/**'; | |
if (empty($table_name)) | |
throw new Exception('table_name parameter is empty', - 2); | |
if (empty($directory)) | |
throw new Exception('directory parameter is empty', - 3); | |
$conn_name = $this->connectionName(); | |
$conn = Db::getConnection($conn_name); | |
if (!isset($prefix)) { | |
$prefix = empty($conn['prefix']) ? '' : $conn['prefix']; | |
} | |
if (!empty($prefix)) { | |
$prefix_len = strlen($prefix); | |
$table_name_base = substr($table_name, $prefix_len); | |
$table_name_prefix = substr($table_name, 0, $prefix_len); | |
if (empty($table_name_base) or $table_name_prefix != $prefix) | |
return ''; // no class generated | |
} else { | |
$table_name_base = $table_name; | |
} | |
if (empty($classname_prefix)) | |
$classname_prefix = ''; | |
if (!isset($class_name_base)) { | |
$class_name_base = Db::generateTableClassName($table_name_base); | |
} | |
$class_name = ucfirst($classname_prefix) . $class_name_base; | |
$table_cols = $this->rawQuery("SHOW COLUMNS FROM $table_name")->execute()->fetchAll(PDO::FETCH_ASSOC); | |
$table_status = $this->rawQuery("SHOW TABLE STATUS WHERE Name = '$table_name'")->execute()->fetchAll(PDO::FETCH_COLUMN, 17); | |
$table_comment = (!empty($table_status[0])) ? " * <br/>{$table_status[0]}\n" : ''; | |
// Calculate primary key | |
$pk = array(); | |
foreach ($table_cols as $table_col) { | |
if ($table_col['Key'] == 'PRI') | |
$pk[] = $table_col['Field']; | |
} | |
$pk_exported = var_export($pk, true); | |
$pk_json = json_encode($pk); | |
// Calculate validation functions | |
$functions = array(); | |
$js_functions = array(); | |
$field_names = array(); | |
$properties = array(); | |
$js_properties = array(); | |
$required_field_names = array(); | |
$magic_field_names = array(); | |
foreach ($table_cols as $table_col) { | |
$is_magic_field = null; | |
$field_name = $table_col['Field']; | |
$field_names[] = $field_name; | |
$field_null = $table_col['Null'] == 'YES' ? true : false; | |
$field_default = $table_col['Default']; | |
$auto_inc = strpos($table_col['Extra'], 'auto_increment') !== false ? true : false; | |
$type = $table_col['Type']; | |
$pieces = explode('(', $type); | |
if (isset($pieces[1])) { | |
$pieces2 = explode(')', $pieces[1]); | |
$pieces2_count = count($pieces2); | |
if ($pieces2_count > 2) { | |
$pieces2 = array( | |
implode(')', array_slice($pieces2, 0, -1)), | |
end($pieces2) | |
); | |
} | |
} | |
$type_name = $pieces[0]; | |
if (isset($pieces2)) { | |
$type_display_range = $pieces2[0]; | |
$type_modifiers = $pieces2[1]; | |
$type_unsigned = (strpos($type_modifiers, 'unsigned') !== false); | |
} | |
switch ($type_name) { | |
case 'tinyint': | |
$type_range_min = $type_unsigned ? 0 : - 128; | |
$type_range_max = $type_unsigned ? 255 : 127; | |
break; | |
case 'smallint': | |
$type_range_min = $type_unsigned ? 0 : - 32768; | |
$type_range_max = $type_unsigned ? 65535 : 32767; | |
break; | |
case 'mediumint': | |
$type_range_min = $type_unsigned ? 0 : - 8388608; | |
$type_range_max = $type_unsigned ? 16777215 : 8388607; | |
break; | |
case 'int': | |
$type_range_min = $type_unsigned ? 0 : - 2147483648; | |
$type_range_max = $type_unsigned ? 4294967295 : 2147483647; | |
break; | |
case 'bigint': | |
$type_range_min = $type_unsigned ? 0 : - 9223372036854775808; | |
$type_range_max = $type_unsigned ? 18446744073709551615 : 9223372036854775807; | |
break; | |
} | |
$null_check = $field_null ? "if (!isset(\$value)) return array('$field_name', \$value);\n\t\t" : ''; | |
$dbe_check = "if (\$value instanceof Db_Expression) return array('$field_name', \$value);\n\t\t"; | |
$js_null_check = $field_null ? "if (!value) return value;\n\t\t" : ''; | |
$js_dbe_check = "if (value instanceof Db.Expression) return value;\n\t\t"; | |
if (! isset($functions["beforeSet_$field_name"])) | |
$functions["beforeSet_$field_name"] = array(); | |
if (! isset($js_functions["beforeSet_$field_name"])) | |
$js_functions["beforeSet_$field_name"] = array(); | |
switch (strtolower($type_name)) { | |
case 'tinyint': | |
case 'smallint': | |
case 'int': | |
case 'mediumint': | |
case 'bigint': | |
$properties[]="integer $field_name"; | |
$js_properties[] = "$field_name integer"; | |
$functions["beforeSet_$field_name"][] = <<<EOT | |
{$null_check}{$dbe_check}if (!is_numeric(\$value) or floor(\$value) != \$value) | |
throw new Exception('Non-integer value being assigned to '.\$this->getTable().".$field_name"); | |
if (\$value < $type_range_min or \$value > $type_range_max) | |
throw new Exception("Out-of-range value '\$value' being assigned to ".\$this->getTable().".$field_name"); | |
EOT; | |
$functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and verifies if integer value falls within allowed limits | |
* @method beforeSet_$field_name | |
* @param {integer} \$value | |
* @return {array} An array of field name and value | |
* @throws {Exception} An exception is thrown if \$value is not integer or does not fit in allowed range | |
*/ | |
EOT; | |
$js_functions["beforeSet_$field_name"][] = <<<EOT | |
{$js_null_check}{$js_dbe_check}value = Number(value); | |
if (isNaN(value) || Math.floor(value) != value) | |
throw new Error('Non-integer value being assigned to '+this.table()+".$field_name"); | |
if (value < $type_range_min || value > $type_range_max) | |
throw new Error("Out-of-range value '"+value+"' being assigned to "+this.table()+".$field_name"); | |
EOT; | |
$js_functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and verifies if integer value falls within allowed limits | |
* @method beforeSet_$field_name | |
* @param {integer} value | |
* @return {integer} The value | |
* @throws {Error} An exception is thrown if 'value' is not integer or does not fit in allowed range | |
*/ | |
EOT; | |
break; | |
case 'enum': | |
$properties[]="mixed $field_name"; | |
$js_properties[] = "$field_name string"; | |
$functions["beforeSet_$field_name"][] = <<<EOT | |
{$null_check}{$dbe_check}if (!in_array(\$value, array($type_display_range))) | |
throw new Exception("Out-of-range value '\$value' being assigned to ".\$this->getTable().".$field_name"); | |
EOT; | |
$functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and verifies if value belongs to enum values list | |
* @method beforeSet_$field_name | |
* @param {string} \$value | |
* @return {array} An array of field name and value | |
* @throws {Exception} An exception is thrown if \$value does not belong to enum values list | |
*/ | |
EOT; | |
$js_functions["beforeSet_$field_name"][] = <<<EOT | |
{$js_null_check}{$js_dbe_check}if ([$type_display_range].indexOf(value) < 0) | |
throw new Error("Out-of-range value '"+value+"' being assigned to "+this.table()+".$field_name"); | |
EOT; | |
$js_functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and verifies if value belongs to enum values list | |
* @method beforeSet_$field_name | |
* @param {string} value | |
* @return {string} The value | |
* @throws {Error} An exception is thrown if 'value' does not belong to enum values list | |
*/ | |
EOT; | |
break; | |
case 'varchar': | |
case 'varbinary': | |
$properties[]="string $field_name"; | |
$js_properties[] = "$field_name string"; | |
$functions["beforeSet_$field_name"][] = <<<EOT | |
{$null_check}{$dbe_check}if (!is_string(\$value) and !is_numeric(\$value)) | |
throw new Exception('Must pass a string to '.\$this->getTable().".$field_name"); | |
if (strlen(\$value) > $type_display_range) | |
throw new Exception('Exceedingly long value being assigned to '.\$this->getTable().".$field_name"); | |
EOT; | |
$functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and verifies if value is string of length within acceptable limit. | |
* Optionally accept numeric value which is converted to string | |
* @method beforeSet_$field_name | |
* @param {string} \$value | |
* @return {array} An array of field name and value | |
* @throws {Exception} An exception is thrown if \$value is not string or is exceedingly long | |
*/ | |
EOT; | |
$js_functions["beforeSet_$field_name"][] = <<<EOT | |
{$js_null_check}{$js_dbe_check}if (typeof value !== "string" && typeof value !== "number") | |
throw new Error('Must pass a string to '+this.table()+".$field_name"); | |
if (typeof value === "string" && value.length > $type_display_range) | |
throw new Error('Exceedingly long value being assigned to '+this.table()+".$field_name"); | |
EOT; | |
$js_functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and verifies if value is string of length within acceptable limit. | |
* Optionally accept numeric value which is converted to string | |
* @method beforeSet_$field_name | |
* @param {string} value | |
* @return {string} The value | |
* @throws {Error} An exception is thrown if 'value' is not string or is exceedingly long | |
*/ | |
EOT; | |
break; | |
case 'date': | |
$properties[]="string|Db_Expression $field_name"; | |
$js_properties[] = "$field_name string|Db.Expression"; | |
$functions["beforeSet_$field_name"][] = <<<EOT | |
{$null_check}{$dbe_check}\$date = date_parse(\$value); | |
if (!empty(\$date['errors'])) | |
throw new Exception("Date \$value in incorrect format being assigned to ".\$this->getTable().".$field_name"); | |
foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as \$v) | |
\$\$v = \$date[\$v]; | |
\$value = sprintf("%04d-%02d-%02d", \$year, \$month, \$day); | |
EOT; | |
$functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and normalize the date string | |
* @method beforeSet_$field_name | |
* @param {string} \$value | |
* @return {array} An array of field name and value | |
* @throws {Exception} An exception is thrown if \$value does not represent valid date | |
*/ | |
EOT; | |
$js_functions["beforeSet_$field_name"][] = <<<EOT | |
{$js_null_check}{$js_dbe_check}value = new Date(value); | |
EOT; | |
$js_functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field | |
* @method beforeSet_$field_name | |
* @param {string} value | |
* @return {Date|Db.Expression} If 'value' is not Db.Expression the current date is returned | |
*/ | |
EOT; | |
break; | |
case 'datetime': | |
$properties[]="string|Db_Expression $field_name"; | |
$js_properties[] = "$field_name string|Db.Expression"; | |
if (in_array($field_name, array('time_created', 'time_updated', 'created_time', 'updated_time'))) { | |
$magic_field_names[] = $field_name; | |
$is_magic_field = true; | |
} | |
$functions["beforeSet_$field_name"][] = <<<EOT | |
{$null_check}{$dbe_check}\$date = date_parse(\$value); | |
if (!empty(\$date['errors'])) | |
throw new Exception("DateTime \$value in incorrect format being assigned to ".\$this->getTable().".$field_name"); | |
foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as \$v) | |
\$\$v = \$date[\$v]; | |
\$value = sprintf("%04d-%02d-%02d %02d:%02d:%02d", \$year, \$month, \$day, \$hour, \$minute, \$second); | |
EOT; | |
$functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field and normalize the DateTime string | |
* @method beforeSet_$field_name | |
* @param {string} \$value | |
* @return {array} An array of field name and value | |
* @throws {Exception} An exception is thrown if \$value does not represent valid DateTime | |
*/ | |
EOT; | |
$js_functions["beforeSet_$field_name"][] = <<<EOT | |
{$js_null_check}{$js_dbe_check}value = new Date(value); | |
EOT; | |
$js_functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field | |
* @method beforeSet_$field_name | |
* @param {string} value | |
* @return {Date|Db.Expression} If 'value' is not Db.Expression the current date is returned | |
*/ | |
EOT; | |
break; | |
case 'timestamp': | |
$properties[]="string $field_name"; | |
$js_properties[] = "$field_name string"; | |
$magic_field_names[] = $field_name; | |
$is_magic_field = true; | |
break; | |
case 'decimal': | |
$properties[]="float $field_name"; | |
$js_properties[] = "$field_name number"; | |
$js_functions["beforeSet_$field_name"][] = <<<EOT | |
{$js_null_check}{$js_dbe_check}value = Number(value); | |
if (isNaN(value)) | |
throw new Error('Non-number value being assigned to '+this.table()+".$field_name"); | |
EOT; | |
$js_functions["beforeSet_$field_name"]['comment'] = <<<EOT | |
$dc | |
* Method is called before setting the field to verify if value is a number | |
* @method beforeSet_$field_name | |
* @param {integer} value | |
* @return {integer} The value | |
* @throws {Error} If 'value' is not number | |
*/ | |
EOT; | |
break; | |
default: | |
$properties[]="mixed $field_name"; | |
$js_properties[] = "$field_name mixed"; | |
break; | |
} | |
if (! empty($functions["beforeSet_$field_name"])) { | |
$functions["beforeSet_$field_name"]['return_statement'] = <<<EOT | |
return array('$field_name', \$value); | |
EOT; | |
} | |
if (! empty($js_functions["beforeSet_$field_name"])) { | |
$js_functions["beforeSet_$field_name"]['return_statement'] = <<<EOT | |
return value; | |
EOT; | |
} | |
if (! $field_null and ! $is_magic_field and ! $auto_inc | |
//and (in_array($type_name, array( | |
// 'tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'enum', | |
//)) | |
and !isset($field_default) | |
//) | |
) { | |
$required_field_names[] = "'$field_name'"; | |
} | |
} | |
$field_names_json = json_encode($field_names); | |
$field_names_json_indented = str_replace( | |
array("[", ",", "]"), | |
array("[\n\t\t", ",\n\t\t", "\n\t]"), | |
$field_names_json | |
); | |
$functions['afterSet'] = array(); | |
$field_names_exported = "\$this->fieldNames()"; | |
$afterSet_code = <<<EOT | |
if (!in_array(\$name, $field_names_exported)) | |
\$this->notModified(\$name); | |
EOT; | |
$return_statement = <<<EOT | |
return \$value; | |
EOT; | |
$functions["afterSet"][] = $afterSet_code; | |
$functions['afterSet']['return_statement'] = $return_statement; | |
$functions['afterSet']['args'] = '$name, $value'; | |
$functions['afterSet']['comment'] = <<<EOT | |
$dc | |
* Method is called after field is set and used to keep \$fields_modified property up to date | |
* @method afterSet | |
* @param {string} \$name The field name | |
* @param {mixed} \$value The value of the field | |
* @return {mixed} Original value | |
*/ | |
EOT; | |
$functions['beforeSave'] = array(); | |
$js_functions['beforeSave'] = array(); | |
if ($required_field_names) { | |
$required_fields_string = implode(',', $required_field_names); | |
$beforeSave_code = <<<EOT | |
if (!\$this->retrieved) { | |
\$table = \$this->getTable(); | |
foreach (array($required_fields_string) as \$name) { | |
if (!isset(\$value[\$name])) { | |
throw new Exception("the field \$table.\$name needs a value, because it is NOT NULL, not auto_increment, and lacks a default value."); | |
} | |
} | |
} | |
EOT; | |
$js_beforeSave_code = <<<EOT | |
var fields = [$required_fields_string], i; | |
if (!this._retrieved) { | |
var table = this.table(); | |
for (i=0; i<fields.length; i++) { | |
if (typeof this.fields[fields[i]] === "undefined") { | |
throw new Error("the field "+table+"."+fields[i]+" needs a value, because it is NOT NULL, not auto_increment, and lacks a default value."); | |
} | |
} | |
} | |
EOT; | |
$return_statement = <<<EOT | |
return \$value; | |
EOT; | |
$js_return_statement = <<<EOT | |
return value; | |
EOT; | |
$functions["beforeSave"][] = $beforeSave_code; | |
$functions['beforeSave']['return_statement'] = $return_statement; | |
$functions['beforeSave']['comment'] = <<<EOT | |
$dc | |
* Check if mandatory fields are set and updates 'magic fields' with appropriate values | |
* @method beforeSave | |
* @param {array} \$value The array of fields | |
* @return {array} | |
* @throws {Exception} If mandatory field is not set | |
*/ | |
EOT; | |
$js_functions["beforeSave"][] = $js_beforeSave_code; | |
$js_functions['beforeSave']['return_statement'] = $js_return_statement; | |
$js_functions['beforeSave']['comment'] = <<<EOT | |
$dc | |
* Check if mandatory fields are set and updates 'magic fields' with appropriate values | |
* @method beforeSave | |
* @param {array} value The array of fields | |
* @return {array} | |
* @throws {Error} If mandatory field is not set | |
*/ | |
EOT; | |
} | |
//$functions['beforeSave'] = array(); | |
if (count($magic_field_names) > 0) { | |
$beforeSave_code = ''; | |
$js_beforeSave_code = ''; | |
foreach (array('time_created', 'created_time') as $cmf) { | |
if (in_array($cmf, $magic_field_names)) { | |
$beforeSave_code .= <<<EOT | |
if (!\$this->retrieved and !isset(\$value['$cmf'])) | |
\$value['$cmf'] = new Db_Expression('CURRENT_TIMESTAMP'); | |
EOT; | |
$js_beforeSave_code .= <<<EOT | |
if (!this._retrieved && !value['$cmf']) | |
value['$cmf'] = new Db.Expression('CURRENT_TIMESTAMP'); | |
EOT; | |
break; | |
} | |
} | |
foreach (array('time_updated', 'updated_time') as $umf) { | |
if (in_array($umf, $magic_field_names)) { | |
$beforeSave_code .= <<<EOT | |
//if (\$this->retrieved and !isset(\$value['$umf'])) | |
// convention: we'll have $umf = $cmf if just created. | |
\$value['$umf'] = new Db_Expression('CURRENT_TIMESTAMP'); | |
EOT; | |
$js_beforeSave_code .= <<<EOT | |
// convention: we'll have $umf = $cmf if just created. | |
value['$umf'] = new Db.Expression('CURRENT_TIMESTAMP'); | |
EOT; | |
break; | |
} | |
} | |
$return_statement = <<<EOT | |
return \$value; | |
EOT; | |
$js_return_statement = <<<EOT | |
return value; | |
EOT; | |
$functions['beforeSave'][] = $beforeSave_code; | |
$functions['beforeSave']['return_statement'] = $return_statement; | |
$js_functions['beforeSave'][] = $js_beforeSave_code; | |
$js_functions['beforeSave']['return_statement'] = $js_return_statement; | |
} | |
$functions['fieldNames'] = array(); | |
$fieldNames_exported = Db_Utils::var_export($field_names); | |
$fieldNames_code = <<<EOT | |
\$field_names = $fieldNames_exported; | |
\$result = \$field_names; | |
if (!empty(\$table_alias)) { | |
\$temp = array(); | |
foreach (\$result as \$field_name) | |
\$temp[] = \$table_alias . '.' . \$field_name; | |
\$result = \$temp; | |
} | |
if (!empty(\$field_alias_prefix)) { | |
\$temp = array(); | |
reset(\$field_names); | |
foreach (\$result as \$field_name) { | |
\$temp[\$field_alias_prefix . current(\$field_names)] = \$field_name; | |
next(\$field_names); | |
} | |
\$result = \$temp; | |
} | |
EOT; | |
$return_statement = <<<EOT | |
return \$result; | |
EOT; | |
$functions['fieldNames'][] = $fieldNames_code; | |
$functions['fieldNames']['return_statement'] = $return_statement; | |
$functions['fieldNames']['args'] = '$table_alias = null, $field_alias_prefix = null'; | |
$functions['fieldNames']['modifiers'] = 'static'; | |
$functions['fieldNames']['comment'] = <<<EOT | |
$dc | |
* Retrieves field names for class table | |
* @method fieldNames | |
* @static | |
* @param {string} [\$table_alias=null] If set, the alieas is added to each field | |
* @param {string} [\$field_alias_prefix=null] If set, the method returns associative array of `'prefixed field' => 'field'` pairs | |
* @return {array} An array of field names | |
*/ | |
EOT; | |
$functions_code = array(); | |
foreach ($functions as $func_name => $func_code) { | |
$func_args = isset($func_code['args']) ? $func_code['args'] : '$value'; | |
$func_modifiers = isset($func_code['modifiers']) ? $func_code['modifiers'].' ' : ''; | |
$func_code_string = isset($func_code['comment']) ? $func_code['comment']."\n" : ''; | |
$func_code_string .= <<<EOT | |
{$func_modifiers}function $func_name($func_args) | |
{ | |
EOT; | |
if (is_array($func_code) and ! empty($func_code)) { | |
foreach ($func_code as $key => $code_tool) { | |
if (is_string($key)) | |
continue; | |
$func_code_string .= $code_tool; | |
} | |
$func_code_string .= "\n" . $func_code['return_statement']; | |
} | |
$func_code_string .= <<<EOT | |
} | |
EOT; | |
if (! empty($func_code)) | |
$functions_code[] = $func_code_string; | |
} | |
$functions_string = implode("\n\n", $functions_code); | |
foreach ($js_functions as $func_name => $func_code) { | |
$func_args = isset($func_code['args']) ? $func_code['args'] : 'value'; | |
$instance = isset($func_code['instance']) ? '.prototype' : ''; | |
$func_code_string = isset($func_code['comment']) ? $func_code['comment']."\n" : ''; | |
$func_code_string .= <<<EOT | |
Base.prototype.$func_name = function ($func_args) { | |
EOT; | |
if (is_array($func_code) and ! empty($func_code)) { | |
foreach ($func_code as $key => $code_tool) { | |
if (is_string($key)) | |
continue; | |
$func_code_string .= $code_tool; | |
} | |
$func_code_string .= "\n" . $func_code['return_statement']; | |
} | |
$func_code_string .= <<<EOT | |
}; | |
EOT; | |
if (! empty($func_code)) | |
$js_functions_code[] = $func_code_string; | |
} | |
$js_functions_string = implode("\n\n", $js_functions_code); | |
$pk_exported_indented = str_replace("\n", "\n\t\t\t", $pk_exported); | |
$pk_json_indented = str_replace( | |
array("[", ",", "]"), | |
array("[\n\t\t", ",\n\t\t", "\n\t]"), | |
$pk_json | |
); | |
$conn_name_var = var_export($conn_name, true); | |
$class_name_var = var_export($class_name, true); | |
$class_name_prefix = rtrim(ucfirst($classname_prefix), "._"); | |
foreach ($properties as $k => $v) { | |
$tmp = explode(' ', $v); | |
$properties[$k] = <<<EOT | |
$dc | |
* @property \${$tmp[1]} | |
* @type $tmp[0] | |
*/ | |
EOT; | |
} | |
foreach ($js_properties as $k => $v) { | |
$tmp = explode(' ', $v); | |
$js_properties[$k] = <<<EOT | |
$dc | |
* @property $tmp[0] | |
* @type $tmp[1] | |
*/ | |
EOT; | |
} | |
$field_hints = implode("\n", $properties); | |
$js_field_hints = implode("\n", $js_properties); | |
// Here is the base class: | |
$base_class_string = <<<EOT | |
<?php | |
$dc | |
* Autogenerated base class representing $table_name_base rows | |
* in the $conn_name database. | |
* | |
* Don't change this file, since it can be overwritten. | |
* Instead, change the $class_name.php file. | |
* | |
* @module $conn_name | |
*/ | |
$dc | |
* Base class representing '$class_name_base' rows in the '$conn_name' database | |
* @class Base_$class_name | |
* @extends Db_Row | |
*/ | |
abstract class Base_$class_name extends Db_Row | |
{ | |
$field_hints | |
$dc | |
* The setUp() method is called the first time | |
* an object of this class is constructed. | |
* @method setUp | |
*/ | |
function setUp() | |
{ | |
\$this->setDb(self::db()); | |
\$this->setTable(self::table()); | |
\$this->setPrimaryKey( | |
$pk_exported_indented | |
); | |
} | |
$dc | |
* Connects to database | |
* @method db | |
* @static | |
* @return {iDb} The database object | |
*/ | |
static function db() | |
{ | |
return Db::connect($conn_name_var); | |
} | |
$dc | |
* Retrieve the table name to use in SQL statement | |
* @method table | |
* @static | |
* @param {boolean} [\$with_db_name=true] Indicates wheather table name shall contain the database name | |
* @return {string|Db_Expression} The table name as string optionally without database name if no table sharding | |
* was started or Db_Expression class with prefix and database name templates is table was sharded | |
*/ | |
static function table(\$with_db_name = true) | |
{ | |
if (Q_Config::get('Db', 'connections', '$conn_name', 'indexes', '$class_name_base', false)) { | |
return new Db_Expression((\$with_db_name ? '{\$dbname}.' : '').'{\$prefix}'.'$table_name_base'); | |
} else { | |
\$conn = Db::getConnection($conn_name_var); | |
\$prefix = empty(\$conn['prefix']) ? '' : \$conn['prefix']; | |
\$table_name = \$prefix . '$table_name_base'; | |
if (!\$with_db_name) | |
return \$table_name; | |
\$db = Db::connect($conn_name_var); | |
return \$db->dbName().'.'.\$table_name; | |
} | |
} | |
$dc | |
* The connection name for the class | |
* @method connectionName | |
* @static | |
* @return {string} The name of the connection | |
*/ | |
static function connectionName() | |
{ | |
return $conn_name_var; | |
} | |
$dc | |
* Create SELECT query to the class table | |
* @method select | |
* @static | |
* @param \$fields {array} The field values to use in WHERE clauseas as | |
* an associative array of `column => value` pairs | |
* @param [\$alias=null] {string} Table alias | |
* @return {Db_Query_Mysql} The generated query | |
*/ | |
static function select(\$fields, \$alias = null) | |
{ | |
if (!isset(\$alias)) \$alias = ''; | |
\$q = self::db()->select(\$fields, self::table().' '.\$alias); | |
\$q->className = $class_name_var; | |
return \$q; | |
} | |
$dc | |
* Create UPDATE query to the class table | |
* @method update | |
* @static | |
* @param [\$alias=null] {string} Table alias | |
* @return {Db_Query_Mysql} The generated query | |
*/ | |
static function update(\$alias = null) | |
{ | |
if (!isset(\$alias)) \$alias = ''; | |
\$q = self::db()->update(self::table().' '.\$alias); | |
\$q->className = $class_name_var; | |
return \$q; | |
} | |
$dc | |
* Create DELETE query to the class table | |
* @method delete | |
* @static | |
* @param [\$table_using=null] {object} If set, adds a USING clause with this table | |
* @param [\$alias=null] {string} Table alias | |
* @return {Db_Query_Mysql} The generated query | |
*/ | |
static function delete(\$table_using = null, \$alias = null) | |
{ | |
if (!isset(\$alias)) \$alias = ''; | |
\$q = self::db()->delete(self::table().' '.\$alias, \$table_using); | |
\$q->className = $class_name_var; | |
return \$q; | |
} | |
$dc | |
* Create INSERT query to the class table | |
* @method insert | |
* @static | |
* @param [\$fields=array()] {object} The fields as an associative array of `column => value` pairs | |
* @param [\$alias=null] {string} Table alias | |
* @return {Db_Query_Mysql} The generated query | |
*/ | |
static function insert(\$fields = array(), \$alias = null) | |
{ | |
if (!isset(\$alias)) \$alias = ''; | |
\$q = self::db()->insert(self::table().' '.\$alias, \$fields); | |
\$q->className = $class_name_var; | |
return \$q; | |
} | |
$dc | |
* Inserts multiple records into a single table, preparing the statement only once, | |
* and executes all the queries. | |
* @method insertManyAndExecute | |
* @static | |
* @param {array} [\$records=array()] The array of records to insert. | |
* (The field names for the prepared statement are taken from the first record.) | |
* You cannot use Db_Expression objects here, because the function binds all parameters with PDO. | |
* @param {array} [\$options=array()] | |
* An associative array of options, including: | |
* | |
* * "chunkSize" {integer} The number of rows to insert at a time. Defaults to 1.<br/> | |
* * "onDuplicateKeyUpdate" {array} You can put an array of fieldname => value pairs here, | |
* which will add an ON DUPLICATE KEY UPDATE clause to the query. | |
* | |
*/ | |
static function insertManyAndExecute(\$records = array(), \$options = array()) | |
{ | |
self::db()->insertManyAndExecute(self::table(), \$records, \$options); | |
} | |
$functions_string | |
}; | |
EOT; | |
// Set the JS code | |
$js_code = <<<EOT | |
$dc | |
* Autogenerated base class representing $table_name_base rows | |
* in the $conn_name database. | |
* | |
* Don't change this file, since it can be overwritten. | |
* Instead, change the $class_name_prefix/$class_name_base.js file. | |
* | |
* @module $conn_name | |
*/ | |
var Q = require('Q'); | |
var Db = Q.require('Db'); | |
var $conn_name = Q.require('$conn_name'); | |
$dc | |
* Base class representing '$class_name_base' rows in the '$conn_name' database | |
* @namespace Base.$class_name_prefix | |
* @class $class_name_base | |
* @extends Db.Row | |
* @constructor | |
* @param {object} [fields={}] The fields values to initialize table row as | |
* an associative array of `{column: value}` pairs | |
*/ | |
function Base (fields) { | |
$dc | |
* The name of the class | |
* @property className | |
* @type string | |
*/ | |
this.className = "$class_name"; | |
} | |
Q.mixin(Base, Q.require('Db/Row')); | |
$js_field_hints | |
$dc | |
* This method uses Db to establish a connection with the information stored in the configuration. | |
* If the this Db object has already been made, it returns this Db object. | |
* @method db | |
* @return {Db} The database connection | |
*/ | |
Base.db = function () { | |
return $conn_name.db(); | |
}; | |
$dc | |
* Retrieve the table name to use in SQL statement | |
* @method table | |
* @param [withoutDbName=false] {boolean} Indicates wheather table name shall contain the database name | |
* @return {string|Db.Expression} The table name as string optionally without database name if no table sharding was started | |
* or Db.Expression object with prefix and database name templates is table was sharded | |
*/ | |
Base.table = function (withoutDbName) { | |
if (Q.Config.get(['Db', 'connections', '$conn_name', 'indexes', '$class_name_base'], false)) { | |
return new Db.Expression((withoutDbName ? '' : '{\$dbname}.')+'{\$prefix}$table_name_base'); | |
} else { | |
var conn = Db.getConnection('$conn_name'); | |
var prefix = conn.prefix || ''; | |
var tableName = prefix + '$table_name_base'; | |
var dbname = Base.table.dbname; | |
if (!dbname) { | |
var dsn = Db.parseDsnString(conn['dsn']); | |
dbname = Base.table.dbname = dsn.dbname; | |
} | |
return withoutDbName ? tableName : dbname + '.' + tableName; | |
} | |
}; | |
$dc | |
* The connection name for the class | |
* @method connectionName | |
* @return {string} The name of the connection | |
*/ | |
Base.connectionName = function() { | |
return '$conn_name'; | |
}; | |
$dc | |
* Create SELECT query to the class table | |
* @method SELECT | |
* @param fields {object|string} The field values to use in WHERE clauseas as an associative array of `{column: value}` pairs | |
* @param [alias=null] {string} Table alias | |
* @return {Db.Query.Mysql} The generated query | |
*/ | |
Base.SELECT = function(fields, alias) { | |
var q = Base.db().SELECT(fields, Base.table()+(alias ? ' '+alias : '')); | |
q.className = '$class_name'; | |
return q; | |
}; | |
$dc | |
* Create UPDATE query to the class table. Use Db.Query.Mysql.set() method to define SET clause | |
* @method UPDATE | |
* @param [alias=null] {string} Table alias | |
* @return {Db.Query.Mysql} The generated query | |
*/ | |
Base.UPDATE = function(alias) { | |
var q = Base.db().UPDATE(Base.table()+(alias ? ' '+alias : '')); | |
q.className = '$class_name'; | |
return q; | |
}; | |
$dc | |
* Create DELETE query to the class table | |
* @method DELETE | |
* @param [table_using=null] {object} If set, adds a USING clause with this table | |
* @param [alias=null] {string} Table alias | |
* @return {Db.Query.Mysql} The generated query | |
*/ | |
Base.DELETE = function(table_using, alias) { | |
var q = Base.db().DELETE(Base.table()+(alias ? ' '+alias : ''), table_using); | |
q.className = '$class_name'; | |
return q; | |
}; | |
$dc | |
* Create INSERT query to the class table | |
* @method INSERT | |
* @param {object} [fields={}] The fields as an associative array of `{column: value}` pairs | |
* @param [alias=null] {string} Table alias | |
* @return {Db.Query.Mysql} The generated query | |
*/ | |
Base.INSERT = function(fields, alias) { | |
var q = Base.db().INSERT(Base.table()+(alias ? ' '+alias : ''), fields || {}); | |
q.className = '$class_name'; | |
return q; | |
}; | |
// Instance methods | |
Base.prototype.setUp = function() { | |
// does nothing for now | |
}; | |
Base.prototype.db = function () { | |
return Base.db(); | |
}; | |
Base.prototype.table = function () { | |
return Base.table(); | |
}; | |
$dc | |
* Retrieves primary key fields names for class table | |
* @method primaryKey | |
* @return {string[]} An array of field names | |
*/ | |
Base.prototype.primaryKey = function () { | |
return $pk_json_indented; | |
}; | |
$dc | |
* Retrieves field names for class table | |
* @method fieldNames | |
* @return {array} An array of field names | |
*/ | |
Base.prototype.fieldNames = function () { | |
return $field_names_json_indented; | |
}; | |
$js_functions_string | |
module.exports = Base; | |
EOT; | |
// Return the base class | |
return $base_class_string; // unless the above line threw an exception | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment