Пример #1
0
 /**
  * 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. 
  * Each record should be an array of ($field => $value) pairs, with the exact
  * same set of keys (field names) in each array.
  * @param {array} [$options=array()] An associative array of options, including:
  * @param {string} [$options.className]
  *    If you provide the class name, the system will be able to use any sharding
  *    indexes under that class name in the config.
  * @param {integer} [$options.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.
  * @param {array} [$options.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())
 {
     // Validate and get options
     if (empty($table_into)) {
         throw new Exception("table not specified in call to 'insertManyAndExecute'.");
     }
     if (empty($records)) {
         return false;
     }
     $chunkSize = isset($options['chunkSize']) ? $options['chunkSize'] : 20;
     if ($chunkSize < 0) {
         return false;
     }
     $onDuplicateKeyUpdate = isset($options['onDuplicateKeyUpdate']) ? $options['onDuplicateKeyUpdate'] : null;
     $className = isset($options['className']) ? $options['className'] : null;
     // Get the columns list
     $record = reset($records);
     foreach ($record as $column => $value) {
         $columns_list[] = Db_Query_Mysql::column($column);
     }
     $columns_string = implode(', ', $columns_list);
     $into = "{$table_into} ({$columns_string})";
     // On duplicate key update clause (optional)
     $update_fields = array();
     $odku_clause = '';
     if (isset($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);
     }
     // Start filling
     $queries = array();
     $queryCounts = array();
     $bindings = array();
     $last_q = array();
     $last_queries = array();
     foreach ($records as $record) {
         // get shard, if any
         $query = new Db_Query_Mysql($this, Db_Query::TYPE_INSERT);
         $shard = '';
         if (isset($className)) {
             $query->className = $className;
             $sharded = $query->shard(null, $record);
             $shard = key($sharded);
             if (count($sharded) > 1 or $shard === '*') {
                 // should be only one shard
                 throw new Exception("Db_Mysql::insertManyAndExecute query should not hit more than one shard: " . Q::json_encode($record));
             }
         }
         // start filling out the query data
         $qc = empty($queryCounts[$shard]) ? 1 : $queryCounts[$shard] + 1;
         if (!isset($bindings[$shard])) {
             $bindings[$shard] = array();
         }
         $values_list = array();
         foreach ($record as $column => $value) {
             if ($value instanceof Db_Expression) {
                 $values_list[] = "{$value}";
             } else {
                 $values_list[] = ':_' . $qc . '_' . $column;
                 $bindings[$shard]['_' . $qc . '_' . $column] = $value;
             }
         }
         $values_string = implode(', ', $values_list);
         if (empty($queryCounts[$shard])) {
             $q = $queries[$shard] = "INSERT INTO {$into}\nVALUES ({$values_string}) ";
             $queryCounts[$shard] = 1;
         } else {
             $q = $queries[$shard] .= ",\n       ({$values_string}) ";
             ++$queryCounts[$shard];
         }
         // if chunk filled up for this shard, execute it
         if ($qc === $chunkSize) {
             if ($onDuplicateKeyUpdate) {
                 $q .= $odku_clause;
             }
             $query = $this->rawQuery($q)->bind($bindings[$shard]);
             if ($onDuplicateKeyUpdate) {
                 $query = $query->bind($update_fields);
             }
             if (isset($last_q[$shard]) and $last_q[$shard] === $q) {
                 // re-use the prepared statement, save round-trips to the db
                 $query->reuseStatement($last_queries[$shard]);
             }
             $query->execute(true);
             $last_q[$shard] = $q;
             $last_queries[$shard] = $query;
             // save for re-use
             $bindings[$shard] = $queries[$shard] = array();
             $queryCounts[$shard] = 0;
         }
     }
     // Now execute the remaining queries, if any
     foreach ($queries as $shard => $q) {
         if (!$q) {
             continue;
         }
         if ($onDuplicateKeyUpdate) {
             $q .= $odku_clause;
         }
         $query = $this->rawQuery($q)->bind($bindings[$shard]);
         if ($onDuplicateKeyUpdate) {
             $query = $query->bind($update_fields);
         }
         if (isset($last_q[$shard]) and $last_q[$shard] === $q) {
             // re-use the prepared statement, save round-trips to the db
             $query->reuseStatement($last_queries[$shard]);
         }
         $query->execute(true);
     }
 }