|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00018 00029 defined('MOODLE_INTERNAL') || die(); 00030 00031 require_once($CFG->libdir.'/ddl/sql_generator.php'); 00032 00036 00037 class oracle_sql_generator extends sql_generator { 00038 00040 00041 public $statement_end = "\n/"; // String to be automatically added at the end of each statement 00042 // Using "/" because the standard ";" isn't good for stored procedures (triggers) 00043 00044 public $number_type = 'NUMBER'; // Proper type for NUMBER(x) in this DB 00045 00046 public $unsigned_allowed = false; // To define in the generator must handle unsigned information 00047 public $default_for_char = ' '; // To define the default to set for NOT NULLs CHARs without default (null=do nothing) 00048 // Using this whitespace here because Oracle doesn't distinguish empty and null! :-( 00049 00050 public $drop_default_value_required = true; //To specify if the generator must use some DEFAULT clause to drop defaults 00051 public $drop_default_value = NULL; //The DEFAULT clause required to drop defaults 00052 00053 public $default_after_null = false; //To decide if the default clause of each field must go after the null clause 00054 00055 public $sequence_extra_code = true; //Does the generator need to add extra code to generate the sequence fields 00056 public $sequence_name = ''; //Particular name for inline sequences in this generator 00057 public $sequence_cache_size = 20; //Size of the sequences values cache (20 = Oracle Default) 00058 00059 public $enum_inline_code = false; //Does the generator need to add inline code in the column definition 00060 00061 public $alter_column_sql = 'ALTER TABLE TABLENAME MODIFY (COLUMNSPECS)'; //The SQL template to alter columns 00062 00068 public function getResetSequenceSQL($table) { 00069 00070 if (is_string($table)) { 00071 $tablename = $table; 00072 $xmldb_table = new xmldb_table($tablename); 00073 } else { 00074 $tablename = $table->getName(); 00075 $xmldb_table = $table; 00076 } 00077 // From http://www.acs.ilstu.edu/docs/oracle/server.101/b10759/statements_2011.htm 00078 $value = (int)$this->mdb->get_field_sql('SELECT MAX(id) FROM {'.$tablename.'}'); 00079 $value++; 00080 00081 $seqname = $this->getSequenceFromDB($xmldb_table); 00082 00083 if (!$seqname) { 00085 $seqname = $this->getNameForObject($table, 'id', 'seq'); 00086 } 00087 00088 return array ("DROP SEQUENCE $seqname", 00089 "CREATE SEQUENCE $seqname START WITH $value INCREMENT BY 1 NOMAXVALUE CACHE $this->sequence_cache_size"); 00090 } 00091 00100 public function getTableName(xmldb_table $xmldb_table, $quoted=true) { 00102 if ($this->temptables->is_temptable($xmldb_table->getName())) { 00103 $tablename = $this->temptables->get_correct_name($xmldb_table->getName()); 00104 } else { 00105 $tablename = $this->prefix . $xmldb_table->getName(); 00106 } 00107 00109 if ($quoted) { 00110 $tablename = $this->getEncQuoted($tablename); 00111 } 00112 00113 return $tablename; 00114 } 00115 00120 public function getCreateTempTableSQL($xmldb_table) { 00121 $this->temptables->add_temptable($xmldb_table->getName()); 00122 $sqlarr = $this->getCreateTableSQL($xmldb_table); 00123 $sqlarr = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE GLOBAL TEMPORARY TABLE $1 ON COMMIT PRESERVE ROWS', $sqlarr); 00124 return $sqlarr; 00125 } 00126 00131 public function getDropTempTableSQL($xmldb_table) { 00132 $sqlarr = $this->getDropTableSQL($xmldb_table); 00133 array_unshift($sqlarr, "TRUNCATE TABLE ". $this->getTableName($xmldb_table)); // oracle requires truncate before being able to drop a temp table 00134 $this->temptables->delete_temptable($xmldb_table->getName()); 00135 return $sqlarr; 00136 } 00137 00141 public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) { 00142 00143 switch ($xmldb_type) { 00144 case XMLDB_TYPE_INTEGER: // From http://www.postgresql.org/docs/7.4/interactive/datatype.html 00145 if (empty($xmldb_length)) { 00146 $xmldb_length = 10; 00147 } 00148 $dbtype = 'NUMBER(' . $xmldb_length . ')'; 00149 break; 00150 case XMLDB_TYPE_FLOAT: 00151 case XMLDB_TYPE_NUMBER: 00152 $dbtype = $this->number_type; 00154 if ($xmldb_length > 38) { 00155 $xmldb_length = 38; 00156 } 00157 if (!empty($xmldb_length)) { 00158 $dbtype .= '(' . $xmldb_length; 00159 if (!empty($xmldb_decimals)) { 00160 $dbtype .= ',' . $xmldb_decimals; 00161 } 00162 $dbtype .= ')'; 00163 } 00164 break; 00165 case XMLDB_TYPE_CHAR: 00166 // Do not use NVARCHAR2 here because it has hardcoded 1333 char limit, 00167 // VARCHAR2 allows us to create larger fields that error out later during runtime 00168 // only when too many non-ascii utf-8 chars present. 00169 $dbtype = 'VARCHAR2'; 00170 if (empty($xmldb_length)) { 00171 $xmldb_length='255'; 00172 } 00173 $dbtype .= '(' . $xmldb_length . ' CHAR)'; // CHAR is required because BYTE is the default 00174 break; 00175 case XMLDB_TYPE_TEXT: 00176 $dbtype = 'CLOB'; 00177 break; 00178 case XMLDB_TYPE_BINARY: 00179 $dbtype = 'BLOB'; 00180 break; 00181 case XMLDB_TYPE_DATETIME: 00182 $dbtype = 'DATE'; 00183 break; 00184 } 00185 return $dbtype; 00186 } 00187 00191 public function getCreateSequenceSQL($xmldb_table, $xmldb_field) { 00192 00193 $results = array(); 00194 00195 $sequence_name = $this->getNameForObject($xmldb_table->getName(), $xmldb_field->getName(), 'seq'); 00196 00197 $sequence = "CREATE SEQUENCE $sequence_name START WITH 1 INCREMENT BY 1 NOMAXVALUE CACHE $this->sequence_cache_size"; 00198 00199 $results[] = $sequence; 00200 00201 $results = array_merge($results, $this->getCreateTriggerSQL ($xmldb_table, $xmldb_field, $sequence_name)); 00202 00203 return $results; 00204 } 00205 00209 public function getCreateTriggerSQL($xmldb_table, $xmldb_field, $sequence_name) { 00210 00211 $trigger_name = $this->getNameForObject($xmldb_table->getName(), $xmldb_field->getName(), 'trg'); 00212 00213 $trigger = "CREATE TRIGGER " . $trigger_name; 00214 $trigger.= "\n BEFORE INSERT"; 00215 $trigger.= "\nON " . $this->getTableName($xmldb_table); 00216 $trigger.= "\n FOR EACH ROW"; 00217 $trigger.= "\nBEGIN"; 00218 $trigger.= "\n IF :new." . $this->getEncQuoted($xmldb_field->getName()) . ' IS NULL THEN'; 00219 $trigger.= "\n SELECT " . $sequence_name . '.nextval INTO :new.' . $this->getEncQuoted($xmldb_field->getName()) . " FROM dual;"; 00220 $trigger.= "\n END IF;"; 00221 $trigger.= "\nEND;"; 00222 00223 return array($trigger); 00224 } 00225 00230 public function getDropSequenceSQL($xmldb_table, $xmldb_field, $include_trigger=false) { 00231 00232 $result = array(); 00233 00234 if ($sequence_name = $this->getSequenceFromDB($xmldb_table)) { 00235 $result[] = "DROP SEQUENCE " . $sequence_name; 00236 } 00237 00238 if ($trigger_name = $this->getTriggerFromDB($xmldb_table) && $include_trigger) { 00239 $result[] = "DROP TRIGGER " . $trigger_name; 00240 } 00241 00242 return $result; 00243 } 00244 00248 function getCommentSQL ($xmldb_table) { 00249 00250 $comment = "COMMENT ON TABLE " . $this->getTableName($xmldb_table); 00251 $comment.= " IS '" . $this->addslashes(substr($xmldb_table->getComment(), 0, 250)) . "'"; 00252 00253 return array($comment); 00254 } 00255 00259 public function getDropTableExtraSQL($xmldb_table) { 00260 $xmldb_field = new xmldb_field('id'); // Fields having sequences should be exclusively, id. 00261 return $this->getDropSequenceSQL($xmldb_table, $xmldb_field, false); 00262 } 00263 00267 public function getRenameTableExtraSQL($xmldb_table, $newname) { 00268 00269 $results = array(); 00270 00271 $xmldb_field = new xmldb_field('id'); // Fields having sequences should be exclusively, id. 00272 00273 $oldseqname = $this->getSequenceFromDB($xmldb_table); 00274 $newseqname = $this->getNameForObject($newname, $xmldb_field->getName(), 'seq'); 00275 00276 $oldtriggername = $this->getTriggerFromDB($xmldb_table); 00277 $newtriggername = $this->getNameForObject($newname, $xmldb_field->getName(), 'trg'); 00278 00280 $results[] = "DROP TRIGGER " . $oldtriggername; 00281 00284 $results[] = 'ALTER SEQUENCE ' . $oldseqname . ' NOCACHE'; 00285 $results[] = 'RENAME ' . $oldseqname . ' TO ' . $newseqname; 00286 $results[] = 'ALTER SEQUENCE ' . $newseqname . ' CACHE ' . $this->sequence_cache_size; 00287 00289 $newt = new xmldb_table($newname); 00290 $results = array_merge($results, $this->getCreateTriggerSQL($newt, $xmldb_field, $newseqname)); 00291 00293 $oldtablename = $this->getTableName($xmldb_table); 00294 $newtablename = $this->getTableName($newt); 00295 00296 $oldconstraintprefix = $this->getNameForObject($xmldb_table->getName(), ''); 00297 $newconstraintprefix = $this->getNameForObject($newt->getName(), '', ''); 00298 00299 if ($constraints = $this->getCheckConstraintsFromDB($xmldb_table)) { 00300 foreach ($constraints as $constraint) { 00302 $results[] = 'ALTER TABLE ' . $newtablename . ' DROP CONSTRAINT ' . $constraint->name; 00303 } 00304 } 00305 00306 return $results; 00307 } 00308 00316 public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) { 00317 00318 $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause; 00319 $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause; 00320 $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause; 00321 00322 $results = array(); 00323 00325 $tablename = $this->getTableName($xmldb_table); 00326 $fieldname = $xmldb_field->getName(); 00327 00329 $meta = $this->mdb->get_columns($xmldb_table->getName()); 00330 $metac = $meta[$fieldname]; 00331 $oldmetatype = $metac->meta_type; 00332 00333 $oldlength = $metac->max_length; 00336 if ($oldmetatype == 'N') { 00337 $uppertablename = strtoupper($tablename); 00338 $upperfieldname = strtoupper($fieldname); 00339 if ($col = $this->mdb->get_record_sql("SELECT cname, precision 00340 FROM col 00341 WHERE tname = ? AND cname = ?", 00342 array($uppertablename, $upperfieldname))) { 00343 $oldlength = $col->precision; 00344 } 00345 } 00346 $olddecimals = empty($metac->scale) ? null : $metac->scale; 00347 $oldnotnull = empty($metac->not_null) ? false : $metac->not_null; 00348 $olddefault = empty($metac->default_value) || strtoupper($metac->default_value) == 'NULL' ? null : $metac->default_value; 00349 00350 $typechanged = true; //By default, assume that the column type has changed 00351 $precisionchanged = true; //By default, assume that the column precision has changed 00352 $decimalchanged = true; //By default, assume that the column decimal has changed 00353 $defaultchanged = true; //By default, assume that the column default has changed 00354 $notnullchanged = true; //By default, assume that the column notnull has changed 00355 00356 $from_temp_fields = false; //By default don't assume we are going to use temporal fields 00357 00359 if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER && $oldmetatype == 'I') || 00360 ($xmldb_field->getType() == XMLDB_TYPE_NUMBER && $oldmetatype == 'N') || 00361 ($xmldb_field->getType() == XMLDB_TYPE_FLOAT && $oldmetatype == 'F') || 00362 ($xmldb_field->getType() == XMLDB_TYPE_CHAR && $oldmetatype == 'C') || 00363 ($xmldb_field->getType() == XMLDB_TYPE_TEXT && $oldmetatype == 'X') || 00364 ($xmldb_field->getType() == XMLDB_TYPE_BINARY && $oldmetatype == 'B')) { 00365 $typechanged = false; 00366 } 00368 if (($xmldb_field->getType() == XMLDB_TYPE_TEXT) || 00369 ($xmldb_field->getType() == XMLDB_TYPE_BINARY) || 00370 ($oldlength == -1) || 00371 ($xmldb_field->getLength() == $oldlength)) { 00372 $precisionchanged = false; 00373 } 00375 if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER) || 00376 ($xmldb_field->getType() == XMLDB_TYPE_CHAR) || 00377 ($xmldb_field->getType() == XMLDB_TYPE_TEXT) || 00378 ($xmldb_field->getType() == XMLDB_TYPE_BINARY) || 00379 (!$xmldb_field->getDecimals()) || 00380 (!$olddecimals) || 00381 ($xmldb_field->getDecimals() == $olddecimals)) { 00382 $decimalchanged = false; 00383 } 00385 if (($xmldb_field->getDefault() === null && $olddefault === null) || 00386 ($xmldb_field->getDefault() === $olddefault) || //Check both equality and 00387 ("'" . $xmldb_field->getDefault() . "'" === $olddefault)) { //Equality with quotes because ADOdb returns the default with quotes 00388 $defaultchanged = false; 00389 } 00390 00392 if (($xmldb_field->getNotnull() === $oldnotnull)) { 00393 $notnullchanged = false; 00394 } 00395 00401 if (($typechanged) || (($oldmetatype == 'N' || $oldmetatype == 'I') && ($precisionchanged || $decimalchanged))) { 00402 $tempcolname = $xmldb_field->getName() . '___tmp'; // Short tmp name, surely not conflicting ever 00403 if (strlen($tempcolname) > 30) { // Safeguard we don't excess the 30cc limit 00404 $tempcolname = 'ongoing_alter_column_tmp'; 00405 } 00407 $skip_notnull_clause = true; 00408 $skip_default_clause = true; 00409 $xmldb_field->setName($tempcolname); 00410 // Drop the temp column, in case it exists (due to one previous failure in conversion) 00411 // really ugly but we cannot enclose DDL into transaction :-( 00412 if (isset($meta[$tempcolname])) { 00413 $results = array_merge($results, $this->getDropFieldSQL($xmldb_table, $xmldb_field)); 00414 } 00416 $results = array_merge($results, $this->getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause, $skip_type_clause, $skip_notnull_clause)); 00418 00419 // From TEXT to integer/number we need explicit conversion 00420 if ($oldmetatype == 'X' && $xmldb_field->GetType() == XMLDB_TYPE_INTEGER) { 00421 $results[] = 'UPDATE ' . $tablename . ' SET ' . $tempcolname . ' = CAST(' . $this->mdb->sql_compare_text($fieldname) . ' AS INT)'; 00422 } else if ($oldmetatype == 'X' && $xmldb_field->GetType() == XMLDB_TYPE_NUMBER) { 00423 $results[] = 'UPDATE ' . $tablename . ' SET ' . $tempcolname . ' = CAST(' . $this->mdb->sql_compare_text($fieldname) . ' AS NUMBER)'; 00424 00425 // Normal cases, implicit conversion 00426 } else { 00427 $results[] = 'UPDATE ' . $tablename . ' SET ' . $tempcolname . ' = ' . $fieldname; 00428 } 00430 $xmldb_field->setName($fieldname); //Set back the original field name 00431 $results = array_merge($results, $this->getDropFieldSQL($xmldb_table, $xmldb_field)); 00433 $results[] = 'ALTER TABLE ' . $tablename . ' RENAME COLUMN ' . $tempcolname . ' TO ' . $fieldname; 00435 $from_temp_fields = true; 00437 $skip_notnull_clause = false; 00438 $skip_default_clause = false; 00440 $skip_type_clause = true; 00442 if (!$xmldb_field->getNotnull()) { 00443 $notnullchanged = false; 00444 } 00446 if ($xmldb_field->getDefault() === null) { 00447 $defaultchanged = false; 00448 } 00449 } 00450 00452 if (!$typechanged && !$precisionchanged && !$decimalchanged) { 00453 $skip_type_clause = true; 00454 } 00455 00458 if (!$notnullchanged) { 00459 $skip_notnull_clause = true; 00460 00461 if ($from_temp_fields && $xmldb_field->getNotnull()) { 00462 $skip_notnull_clause = false; 00463 } 00464 } 00467 if (!$defaultchanged) { 00468 $skip_default_clause = true; 00469 00470 if ($from_temp_fields) { 00471 $default_clause = $this->getDefaultClause($xmldb_field); 00472 if ($default_clause) { 00473 $skip_notnull_clause = false; 00474 } 00475 } 00476 } 00477 00479 if (!$skip_type_clause || !$skip_notnull_clause || !$skip_default_clause) { 00480 $results = array_merge($results, parent::getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause, $skip_default_clause, $skip_notnull_clause)); 00481 return $results; 00482 } 00483 00485 return $results; 00486 } 00487 00494 public function getDropEnumSQL($xmldb_table, $xmldb_field) { 00496 if ($check_constraints = $this->getCheckConstraintsFromDB($xmldb_table, $xmldb_field)) { 00497 $check_constraint = array_shift($check_constraints); 00498 $constraint_name = strtolower($check_constraint->name); 00499 00500 return array('ALTER TABLE ' . $this->getTableName($xmldb_table) . 00501 ' DROP CONSTRAINT ' . $constraint_name); 00502 } else { 00503 return array(); 00504 } 00505 } 00506 00511 public function getCreateDefaultSQL($xmldb_table, $xmldb_field) { 00514 return $this->getAlterFieldSQL($xmldb_table, $xmldb_field); 00515 } 00516 00521 public function getDropDefaultSQL($xmldb_table, $xmldb_field) { 00524 return $this->getAlterFieldSQL($xmldb_table, $xmldb_field); 00525 } 00526 00537 public function getCheckConstraintsFromDB($xmldb_table, $xmldb_field = null) { 00538 00539 $results = array(); 00540 00541 $tablename = strtoupper($this->getTableName($xmldb_table)); 00542 00543 if ($constraints = $this->mdb->get_records_sql("SELECT lower(c.constraint_name) AS name, c.search_condition AS description 00544 FROM user_constraints c 00545 WHERE c.table_name = ? 00546 AND c.constraint_type = 'C' 00547 AND c.constraint_name not like 'SYS%'", 00548 array($tablename))) { 00549 foreach ($constraints as $constraint) { 00550 $results[$constraint->name] = $constraint; 00551 } 00552 } 00553 00555 if ($xmldb_field) { 00556 $filtered_results = array(); 00557 $filter = $xmldb_field->getName(); 00559 foreach ($results as $key => $result) { 00561 if (preg_match("/^{$filter} IN/i", $result->description)) { 00562 $filtered_results[$key] = $result; 00563 } 00564 } 00566 $results = $filtered_results; 00567 } 00568 00569 return $results; 00570 } 00571 00580 public function getSequenceFromDB($xmldb_table) { 00581 00582 $tablename = strtoupper($this->getTableName($xmldb_table)); 00583 $prefixupper = strtoupper($this->prefix); 00584 $sequencename = false; 00585 00586 if ($trigger = $this->mdb->get_record_sql("SELECT trigger_name, trigger_body 00587 FROM user_triggers 00588 WHERE table_name = ? AND trigger_name LIKE ?", 00589 array($tablename, "{$prefixupper}%_ID%_TRG"))) { 00591 preg_match('/.*SELECT (.*)\.nextval/i', $trigger->trigger_body, $matches); 00592 if (isset($matches[1])) { 00593 $sequencename = $matches[1]; 00594 } 00595 } 00596 00597 return $sequencename; 00598 } 00599 00605 public function getTriggerFromDB($xmldb_table) { 00606 00607 $tablename = strtoupper($this->getTableName($xmldb_table)); 00608 $prefixupper = strtoupper($this->prefix); 00609 $triggername = false; 00610 00611 if ($trigger = $this->mdb->get_record_sql("SELECT trigger_name, trigger_body 00612 FROM user_triggers 00613 WHERE table_name = ? AND trigger_name LIKE ?", 00614 array($tablename, "{$prefixupper}%_ID%_TRG"))) { 00615 $triggername = $trigger->trigger_name; 00616 } 00617 00618 return $triggername; 00619 } 00620 00626 public function isNameInUse($object_name, $type, $table_name) { 00627 switch($type) { 00628 case 'ix': 00629 case 'uix': 00630 case 'seq': 00631 case 'trg': 00632 if ($check = $this->mdb->get_records_sql("SELECT object_name 00633 FROM user_objects 00634 WHERE lower(object_name) = ?", array(strtolower($object_name)))) { 00635 return true; 00636 } 00637 break; 00638 case 'pk': 00639 case 'uk': 00640 case 'fk': 00641 case 'ck': 00642 if ($check = $this->mdb->get_records_sql("SELECT constraint_name 00643 FROM user_constraints 00644 WHERE lower(constraint_name) = ?", array(strtolower($object_name)))) { 00645 return true; 00646 } 00647 break; 00648 } 00649 return false; //No name in use found 00650 } 00651 00652 public function addslashes($s) { 00653 // do not use php addslashes() because it depends on PHP quote settings! 00654 $s = str_replace("'", "''", $s); 00655 return $s; 00656 } 00657 00661 public static function getReservedWords() { 00664 $reserved_words = array ( 00665 'access', 'add', 'all', 'alter', 'and', 'any', 00666 'as', 'asc', 'audit', 'between', 'by', 'char', 00667 'check', 'cluster', 'column', 'comment', 00668 'compress', 'connect', 'create', 'current', 00669 'date', 'decimal', 'default', 'delete', 'desc', 00670 'distinct', 'drop', 'else', 'exclusive', 'exists', 00671 'file', 'float', 'for', 'from', 'grant', 'group', 00672 'having', 'identified', 'immediate', 'in', 00673 'increment', 'index', 'initial', 'insert', 00674 'integer', 'intersect', 'into', 'is', 'level', 00675 'like', 'lock', 'long', 'maxextents', 'minus', 00676 'mlslabel', 'mode', 'modify', 'nchar', 'nclob', 'noaudit', 00677 'nocompress', 'not', 'nowait', 'null', 'number', 'nvarchar2', 00678 'of', 'offline', 'on', 'online', 'option', 'or', 00679 'order', 'pctfree', 'prior', 'privileges', 00680 'public', 'raw', 'rename', 'resource', 'revoke', 00681 'row', 'rowid', 'rownum', 'rows', 'select', 00682 'session', 'set', 'share', 'size', 'smallint', 00683 'start', 'successful', 'synonym', 'sysdate', 00684 'table', 'then', 'to', 'trigger', 'uid', 'union', 00685 'unique', 'update', 'user', 'validate', 'values', 00686 'varchar', 'varchar2', 'view', 'whenever', 00687 'where', 'with' 00688 ); 00689 return $reserved_words; 00690 } 00691 }