public function saveItems(array $itemData, EarthIT_Schema_ResourceClass $rc, array $options = array())
 {
     if (count($itemData) == 0) {
         // Save a few processor cycles
         return $options[EarthIT_Storage_ItemSaver::RETURN_SAVED] ? array() : null;
     }
     EarthIT_Storage_Util::defaultSaveItemsOptions($options);
     $rcName = $rc->getName();
     $blankItem = EarthIT_Storage_Util::defaultItem($rc, true);
     $savedItems = array();
     foreach ($itemData as $item) {
         $id = EarthIT_Storage_Util::itemId($item, $rc);
         if ($id === null) {
             $pk = $rc->getPrimaryKey();
             if ($pk !== null) {
                 foreach ($pk->getFieldNames() as $fn) {
                     if (!isset($item[$fn])) {
                         if ($rc->getField($fn)->getType()->getName() == 'entity ID') {
                             $item[$fn] = call_user_func($this->idGenerator);
                         } else {
                             throw new Exception("Don't know how to generate ID component '{$fn}' of '" . $rc->getName() . "'");
                         }
                     }
                 }
             }
             $id = EarthIT_Storage_Util::itemId($item, $rc);
             if ($id === null) {
                 // Note: In some cases we may want to allow ID-less records,
                 // in which case remove the throw and just append the item to wherever lists.
                 throw new Exception("Failed to generate an ID for a " . $rc->getName());
             }
         }
         $item = EarthIT_Storage_Util::castItemFieldValues($item, $rc);
         if (isset($this->items[$rcName][$id])) {
             switch ($odk = $options[EarthIT_Storage_ItemSaver::ON_DUPLICATE_KEY]) {
                 case EarthIT_Storage_ItemSaver::ODK_ERROR:
                 case EarthIT_Storage_ItemSaver::ODK_UNDEFINED:
                     throw new Exception("saveItem causes collision for " . $rc->getName() . " '{$id}'");
                 case EarthIT_Storage_ItemSaver::ODK_KEEP:
                     $savedItems[$id] = $this->items[$rcName][$id];
                     break;
                 case EarthIT_Storage_ItemSaver::ODK_REPLACE:
                     $savedItems[$id] = $this->items[$rcName][$id] = $item + $blankItem;
                     break;
                 case EarthIT_Storage_ItemSaver::ODK_UPDATE:
                     $savedItems[$id] = $this->items[$rcName][$id] = $item + $this->items[$rcName][$id];
                     break;
                 default:
                     throw new Exception("Unrecognized on-duplicate-key option: '{$odk}'");
             }
         } else {
             $savedItems[$id] = $this->items[$rcName][$id] = $item + $blankItem;
         }
     }
     return $options[EarthIT_Storage_ItemSaver::RETURN_SAVED] ? $savedItems : null;
 }
 protected function _bulkInsertQueries(array $itemData, EarthIT_Schema_ResourceClass $rc, $returnSaved)
 {
     $storableFields = EarthIT_Storage_Util::storableFields($rc);
     $defaultItem = EarthIT_Storage_Util::defaultItem($rc);
     foreach ($itemData as &$item) {
         $item += $defaultItem;
     }
     unset($item);
     $fieldsToStore = self::ensureSameFieldsGivenForAllItems($itemData, $storableFields);
     $params = array();
     $params['table'] = EarthIT_DBC_SQLExpressionUtil::tableExpression($rc, $this->dbObjectNamer);
     $paramCounter = 0;
     $columnNameParams = array();
     $columnNamePlaceholders = array();
     $toStoreColumnNamePlaceholders = array();
     // Only the ones we're specifying in our insert
     foreach ($storableFields as $fn => $f) {
         $columnNameParam = "c_" . $paramCounter++;
         $columnNameParams[$fn] = $columnNameParam;
         $params[$columnNameParam] = new EarthIT_DBC_SQLIdentifier($this->dbObjectNamer->getColumnName($rc, $f));
         $columnNamePlaceholder = "{{$columnNameParam}}";
         $columnNamePlaceholders[$fn] = $columnNamePlaceholder;
     }
     foreach ($fieldsToStore as $fn => $f) {
         $columnNameParam = $columnNameParams[$fn];
         $toStoreColumnNamePlaceholders[] = $columnNamePlaceholders[$fn];
     }
     $columnDbExternalValueSqls = array();
     if ($returnSaved) {
         foreach ($storableFields as $fn => $f) {
             $t = $this->dbInternalToExternalValueSql($f, $rc, $columnNamePlaceholders[$fn]);
             if ($t != $columnNamePlaceholders[$fn]) {
                 $t .= " AS " . $columnNamePlaceholders[$fn];
             }
             $columnDbExternalValueSqls[$fn] = $t;
         }
     }
     if (count($fieldsToStore) == 0) {
         if ($returnSaved) {
             throw new Exception("Returning saved records not supported for MySQL.");
         }
         // INSERT INTO ... DEFAULT VALUES doesn't seem to have a bulk form,
         // so we'll have to make multiple queries.
         // Fortunately they're pretty simple queries (actually just the same one repeated).
         return array_fill(0, count($itemData), new EarthIT_Storage_StorageQuery("INSERT INTO {table} DEFAULT VALUES", $params, $returnSaved));
     }
     $valueRows = array();
     foreach ($itemData as $item) {
         $valueSqls = array();
         foreach ($fieldsToStore as $fn => $f) {
             $paramName = "v_" . $paramCounter++;
             $value = isset($item[$fn]) ? $item[$fn] : null;
             if ($value instanceof EarthIT_DBC_SQLQueryComponent) {
                 $isAlreadyInternal = true;
             } else {
                 if ($value instanceof EarthIT_Storage_InternalValue) {
                     $isAlreadyInternal = true;
                     $value = $value->getValue();
                 } else {
                     $isAlreadyInternal = false;
                 }
             }
             if ($isAlreadyInternal) {
                 $valueSqls[] = "{{$paramName}}";
                 $params[$paramName] = $value;
             } else {
                 $valueSqls[] = $this->dbExternalToInternalValueSql($f, $rc, "{{$paramName}}");
                 $params[$paramName] = $this->schemaToDbExternalValue($value, $f, $rc);
             }
         }
         $valueRows[] = "(" . implode(', ', $valueSqls) . ")";
     }
     $sql = "INSERT INTO {table}\n" . "(" . implode(", ", $toStoreColumnNamePlaceholders) . ") VALUES\n" . implode(",\n", $valueRows);
     if ($returnSaved) {
         $sql .= "\nRETURNING " . implode(', ', $columnDbExternalValueSqls);
     }
     return array(new EarthIT_Storage_StorageQuery($sql, $params, $returnSaved));
 }