From: skodak Date: Mon, 25 Aug 2008 21:00:47 +0000 (+0000) Subject: MDL-15671, MDL-15717 DB->import_record() support - code based on patch by Andrei... X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=94898738ffef002245f8780b285bdaaec3dcc331;p=moodle.git MDL-15671, MDL-15717 DB->import_record() support - code based on patch by Andrei Bautu; added new param to raw insert; unit tests included --- diff --git a/lib/dml/adodb_moodle_database.php b/lib/dml/adodb_moodle_database.php index b0b5de0203..e07e933676 100644 --- a/lib/dml/adodb_moodle_database.php +++ b/lib/dml/adodb_moodle_database.php @@ -238,13 +238,22 @@ abstract class adodb_moodle_database extends moodle_database { * @param mixed $params data record as object or array * @param bool $returnit return it of inserted record * @param bool $bulk true means repeated inserts expected + * @param bool $customsequence true if 'id' included in $params, disables $returnid * @return mixed success or new id */ - public function insert_record_raw($table, $params, $returnid=true, $bulk=false) { + public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { if (!is_array($params)) { $params = (array)$params; } - unset($params['id']); + + if ($customsequence) { + if (!isset($params['id'])) { + return false; + } + $returnid = false; + } else { + unset($params['id']); + } if (empty($params)) { return false; diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index 605b939796..b37ef39361 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -984,9 +984,10 @@ abstract class moodle_database { * @param mixed $params data record as object or array * @param bool $returnit return it of inserted record * @param bool $bulk true means repeated inserts expected + * @param bool $customsequence true if 'id' included in $params, disables $returnid * @return mixed success or new id */ - public abstract function insert_record_raw($table, $params, $returnid=true, $bulk=false); + public abstract function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false); /** * Insert a record into a table and return the "id" field if required. @@ -1001,6 +1002,16 @@ abstract class moodle_database { */ public abstract function insert_record($table, $dataobject, $returnid=true, $bulk=false); + /** + * Import a record into a table, id field is required. + * Safety checks are NOT carried out. Lobs are supported. + * + * @param string $table name of database table to be inserted into + * @param object $dataobject A data object with values for one or more fields in the record + * @return bool success + */ + public abstract function import_record($table, $dataobject); + /** * Update record in database, as fast as possible, no safety checks, lobs not supported. * @param string $table name diff --git a/lib/dml/mssql_adodb_moodle_database.php b/lib/dml/mssql_adodb_moodle_database.php index f350044642..c9350e1450 100644 --- a/lib/dml/mssql_adodb_moodle_database.php +++ b/lib/dml/mssql_adodb_moodle_database.php @@ -365,4 +365,58 @@ class mssql_adodb_moodle_database extends adodb_moodle_database { } return $this->change_database_structure("DBCC CHECKIDENT ('$this->prefix$table', RESEED, $value)"); } + + + /** + * Import a record into a table, id field is required. + * Basic safety checks only. Lobs are supported. + * @param string $table name of database table to be inserted into + * @param mixed $dataobject object or array with fields in the record + * @return bool success + */ + public function import_record($table, $dataobject) { + $dataobject = (object)$dataobject; + + $columns = $this->get_columns($table); + $cleaned = array(); + $blobs = array(); + + foreach ($dataobject as $field=>$value) { + if (!isset($columns[$field])) { // Non-existing table field, skip it + continue; + } + $column = $columns[$field]; + if ($column->meta_type == 'B') { // BLOBs (IMAGE) columns need to be updated apart + if (!is_null($value)) { // If value not null, add it to the list of BLOBs to update later + $blobs[$field] = $value; + $value = null; // Set the default value to be inserted in first instance + } + } else if ($column->meta_type == 'X') { // MSSQL doesn't cast from int to text, so if text column + if (is_numeric($value)) { // and is numeric value + $value = (string)$value; // cast to string + } + } + + $cleaned[$field] = $value; + } + + if (!$this->insert_record_raw($table, $cleaned, false, true, true)) { + return false; + } + + if (empty($blobs)) { + return true; + } + + /// We have BLOBs to postprocess + + foreach ($blobs as $key=>$value) { + $this->writes++; + if (!$this->adodb->UpdateBlob($this->prefix.$table, $key, $value, "id = $id")) { + return false; + } + } + + return true; + } } diff --git a/lib/dml/mysqli_adodb_moodle_database.php b/lib/dml/mysqli_adodb_moodle_database.php index a3f2cb2ca9..a231208b7d 100644 --- a/lib/dml/mysqli_adodb_moodle_database.php +++ b/lib/dml/mysqli_adodb_moodle_database.php @@ -289,4 +289,31 @@ class mysqli_adodb_moodle_database extends adodb_moodle_database { $value++; return $this->change_database_structure("ALTER TABLE $this->prefix$table AUTO_INCREMENT = $value"); } + + /** + * Import a record into a table, id field is required. + * Basic safety checks only. Lobs are supported. + * @param string $table name of database table to be inserted into + * @param mixed $dataobject object or array with fields in the record + * @return bool success + */ + public function import_record($table, $dataobject) { + $dataobject = (object)$dataobject; + + if (empty($dataobject->id)) { + return false; + } + + $columns = $this->get_columns($table); + $cleaned = array(); + + foreach ($dataobject as $field=>$value) { + if (!isset($columns[$field])) { + continue; + } + $cleaned[$field] = $value; + } + + return $this->insert_record_raw($table, $cleaned, false, true, true); + } } \ No newline at end of file diff --git a/lib/dml/oci8po_adodb_moodle_database.php b/lib/dml/oci8po_adodb_moodle_database.php index 1b754713be..3d02f662d8 100644 --- a/lib/dml/oci8po_adodb_moodle_database.php +++ b/lib/dml/oci8po_adodb_moodle_database.php @@ -450,13 +450,22 @@ class oci8po_adodb_moodle_database extends adodb_moodle_database { * @param mixed $params data record as object or array * @param bool $returnit return it of inserted record * @param bool $bulk true means repeated inserts expected + * @param bool $customsequence true if 'id' included in $params, disables $returnid * @return mixed success or new id */ - public function insert_record_raw($table, $params, $returnid=true, $bulk=false) { + public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { if (!is_array($params)) { $params = (array)$params; } - unset($params['id']); + + if ($customsequence) { + if (!isset($params['id'])) { + return false; + } + $returnid = false; + } else { + unset($params['id']); + } if ($returnid) { $dbman = $this->get_manager(); @@ -631,4 +640,71 @@ class oci8po_adodb_moodle_database extends adodb_moodle_database { $this->change_database_structure("DROP SEQUENCE $seqname"); return $this->change_database_structure("CREATE SEQUENCE $seqname START WITH $value INCREMENT BY 1 NOMAXVALUE"); } + + /** + * Import a record into a table, id field is required. + * Basic safety checks only. Lobs are supported. + * @param string $table name of database table to be inserted into + * @param mixed $dataobject object or array with fields in the record + * @return bool success + */ + public function import_record($table, $dataobject) { + $dataobject = (object)$dataobject; + + $columns = $this->get_columns($table); + $cleaned = array(); + $blobs = array(); + $clobs = array(); + + foreach ($dataobject as $field=>$value) { + if (!isset($columns[$field])) { /// Non-existing table field, skip it + continue; + } + /// Apply Oracle dirty hack to value, to have "correct" empty values for Oracle + $value = $this->oracle_dirty_hack($table, $field, $value); + + /// Get column metadata + $column = $columns[$field]; + if ($column->meta_type == 'B') { /// BLOBs columns need to be updated apart + if (!is_null($value)) { /// If value not null, add it to the list of BLOBs to update later + $blobs[$field] = $value; + $value = 'empty_blob()'; /// Set the default value to be inserted (preparing lob storage for next update) + } + + } else if ($column->meta_type == 'X' && strlen($value) > 4000) { /// CLOB columns need to be updated apart (if lenght > 4000) + if (!is_null($value)) { /// If value not null, add it to the list of BLOBs to update later + $clobs[$field] = $value; + $value = 'empty_clob()'; /// Set the default value to be inserted (preparing lob storage for next update) + } + } + + $cleaned[$field] = $value; + } + + if (!$this->insert_record_raw($table, $cleaned, false, true, true)) { + return false; + } + + if (empty($blobs) and empty($clobs)) { + return true; + } + + /// We have BLOBs or CLOBs to postprocess + + foreach ($blobs as $key=>$value) { + $this->writes++; + if (!$this->adodb->UpdateBlob($this->prefix.$table, $key, $value, "id = $id")) { + return false; + } + } + + foreach ($clobs as $key=>$value) { + $this->writes++; + if (!$this->adodb->UpdateClob($this->prefix.$table, $key, $value, "id = $id")) { + return false; + } + } + + return true; + } } diff --git a/lib/dml/pdo_moodle_database.php b/lib/dml/pdo_moodle_database.php index fbc8369fe7..7e150ee69e 100644 --- a/lib/dml/pdo_moodle_database.php +++ b/lib/dml/pdo_moodle_database.php @@ -57,7 +57,7 @@ abstract class pdo_moodle_database extends moodle_database { protected function get_dsn() { return 'mysql:host='.$this->dbhost.';dbname='.$this->dbname; } - + /** * Returns the driver-dependent connection attributes for PDO based on members stored by connect. * Must be called after $dbname, $dbhost, etc. members have been set. @@ -66,7 +66,7 @@ abstract class pdo_moodle_database extends moodle_database { protected function get_pdooptions() { return array(PDO::ATTR_PERSISTENT => $this->dbpersist); } - + protected function configure_dbconnection() { ///TODO: not needed preconfigure_dbconnection() stuff for PDO drivers? } @@ -86,7 +86,7 @@ abstract class pdo_moodle_database extends moodle_database { * @return string */ public function get_name() { - return get_string($this->get_dbtype() . '_pdo', 'install'); + return get_string($this->get_dbtype() . '_pdo', 'install'); } /** @@ -112,7 +112,7 @@ abstract class pdo_moodle_database extends moodle_database { } catch(PDOException $ex) {} return $result; } - + /** * Returns supported query parameter types * @return bitmask @@ -120,7 +120,7 @@ abstract class pdo_moodle_database extends moodle_database { protected function allowed_param_types() { return SQL_PARAMS_QM | SQL_PARAMS_NAMED; } - + /** * Returns last error reported by database engine. */ @@ -170,7 +170,7 @@ abstract class pdo_moodle_database extends moodle_database { } echo '
'; } - + /** * Do NOT use in code, to be used by database_manager only! * @param string $sql query @@ -201,10 +201,10 @@ abstract class pdo_moodle_database extends moodle_database { } /** - * Factory method that creates a recordset for return by a query. The generic pdo_moodle_recordset + * Factory method that creates a recordset for return by a query. The generic pdo_moodle_recordset * class should fit most cases, but pdo_moodle_database subclasses can overide this method to return * a subclass of pdo_moodle_recordset. - * @param object $sth instance of PDOStatement + * @param object $sth instance of PDOStatement * @return object instance of pdo_moodle_recordset */ protected function create_recordset($sth) { @@ -267,7 +267,7 @@ abstract class pdo_moodle_database extends moodle_database { return false; } } - + /** * Returns the sql statement with clauses to append used to limit a recordset range. * @param string $sql the SQL statement to limit. @@ -334,13 +334,22 @@ abstract class pdo_moodle_database extends moodle_database { * @param mixed $params data record as object or array * @param bool $returnit return it of inserted record * @param bool $bulk true means repeated inserts expected + * @param bool $customsequence true if 'id' included in $params, disables $returnid * @return mixed success or new id */ - public function insert_record_raw($table, $params, $returnid=true, $bulk=false) { + public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { if (!is_array($params)) { $params = (array)$params; } - unset($params['id']); + + if ($customsequence) { + if (!isset($params['id'])) { + return false; + } + $returnid = false; + } else { + unset($params['id']); + } if (empty($params)) { return false; @@ -364,7 +373,7 @@ abstract class pdo_moodle_database extends moodle_database { } return false; } - + /** * Insert a record into a table and return the "id" field if required, * Some conversions and safety checks are carried out. Lobs are supported. @@ -409,7 +418,7 @@ abstract class pdo_moodle_database extends moodle_database { return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); } - + /** * Update record in database, as fast as possible, no safety checks, lobs not supported. * @param string $table name @@ -564,4 +573,26 @@ abstract class pdo_moodle_database extends moodle_database { return false; } } + + /** + * Import a record into a table, id field is required. + * Basic safety checks only. Lobs are supported. + * @param string $table name of database table to be inserted into + * @param mixed $dataobject object or array with fields in the record + * @return bool success + */ + public function import_record($table, $dataobject) { + $dataobject = (object)$dataobject; + + $columns = $this->get_columns($table); + $cleaned = array(); + foreach ($dataobject as $field=>$value) { + if (!isset($columns[$field])) { + continue; + } + $cleaned[$field] = $value; + } + + return $this->insert_record_raw($table, $cleaned, false, true, true); + } } diff --git a/lib/dml/postgres7_adodb_moodle_database.php b/lib/dml/postgres7_adodb_moodle_database.php index 9170b5c179..0710cec63f 100644 --- a/lib/dml/postgres7_adodb_moodle_database.php +++ b/lib/dml/postgres7_adodb_moodle_database.php @@ -163,24 +163,34 @@ class postgres7_adodb_moodle_database extends adodb_moodle_database { * @param mixed $params data record as object or array * @param bool $returnit return it of inserted record * @param bool $bulk true means repeated inserts expected + * @param bool $customsequence true if 'id' included in $params, disables $returnid * @return mixed success or new id */ - public function insert_record_raw($table, $params, $returnid=true, $bulk=false) { - /// Postgres doesn't have the concept of primary key built in - /// and will return the OID which isn't what we want. - /// The efficient and transaction-safe strategy is to - /// move the sequence forward first, and make the insert - /// with an explicit id. - + public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { if (!is_array($params)) { $params = (array)$params; } - unset($params['id']); - if ($returnid) { - $this->reads++; - $seqname = "{$this->prefix}{$table}_id_seq"; - if ($nextval = $this->adodb->GenID($seqname)) { - $params['id'] = (int)$nextval; + + if ($customsequence) { + if (!isset($params['id'])) { + return false; + } + $returnid = false; + + } else { + /// Postgres doesn't have the concept of primary key built in + /// and will return the OID which isn't what we want. + /// The efficient and transaction-safe strategy is to + /// move the sequence forward first, and make the insert + /// with an explicit id. + if ($returnid) { + $this->reads++; + $seqname = "{$this->prefix}{$table}_id_seq"; + if ($nextval = $this->adodb->GenID($seqname)) { + $params['id'] = (int)$nextval; + } + } else { + unset($params['id']); } } @@ -476,4 +486,53 @@ class postgres7_adodb_moodle_database extends adodb_moodle_database { $value++; return $this->change_database_structure("ALTER SEQUENCE $this->prefix{$table}_id_seq RESTART WITH $value"); } + + /** + * Import a record into a table, id field is required. + * Basic safety checks only. Lobs are supported. + * @param string $table name of database table to be inserted into + * @param mixed $dataobject object or array with fields in the record + * @return bool success + */ + public function import_record($table, $dataobject) { + $dataobject = (object)$dataobject; + + $columns = $this->get_columns($table); + $cleaned = array(); + $blobs = array(); + + foreach ($dataobject as $field=>$value) { + if (!isset($columns[$field])) { + continue; + } + $column = $columns[$field]; + if ($column->meta_type == 'B') { + if (!is_null($value)) { + $blobs[$field] = $value; + $cleaned[$field] = '@#BLOB#@'; + continue; + } + } + $cleaned[$field] = $value; + } + + if (!$this->insert_record_raw($table, $cleaned, false, true, true)) { + return false; + } + + if (empty($blobs)) { + return true; + } + + /// We have BLOBs to postprocess + + foreach ($blobs as $key=>$value) { + $this->writes++; + if (!$this->adodb->UpdateBlob($this->prefix.$table, $key, $value, "id = $id", 'BLOB')) { // adodb does not use bound parameters for blob updates :-( + return false; + } + } + + return true; + } } \ No newline at end of file diff --git a/lib/dml/simpletest/testdml.php b/lib/dml/simpletest/testdml.php index 61d6255884..c4e54bd24c 100755 --- a/lib/dml/simpletest/testdml.php +++ b/lib/dml/simpletest/testdml.php @@ -992,6 +992,53 @@ class dml_test extends UnitTestCase { } + public function test_import_record() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table($dbman, "testtable"); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + $this->tables[$table->getName()] = $table; + + $record = (object)array('id'=>666, 'course'=>10); + $this->assertTrue($DB->import_record('testtable', $record)); + $records = $DB->get_records('testtable'); + $this->assertEqual(1, count($records)); + $this->assertEqual(10, $records[666]->course); + + $record = (object)array('id'=>13, 'course'=>2); + $this->assertTrue($DB->import_record('testtable', $record)); + $records = $DB->get_records('testtable'); + $this->assertEqual(2, $records[13]->course); + } + + public function test_reset_sequence() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table($dbman, "testtable"); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + $this->tables[$table->getName()] = $table; + + $record = (object)array('id'=>666, 'course'=>10); + $DB->import_record('testtable', $record); + $DB->delete_records('testtable'); + + $this->assertTrue($DB->reset_sequence('testtable')); + $this->assertEqual(1, $DB->insert_record('testtable', (object)array('course'=>13))); + + $DB->import_record('testtable', $record); + $this->assertTrue($DB->reset_sequence('testtable')); + $this->assertEqual(667, $DB->insert_record('testtable', (object)array('course'=>13))); + } + + public function test_insert_record_clob() { global $CFG; @@ -1337,9 +1384,9 @@ class dml_test extends UnitTestCase { function test_sql_position() { $DB = $this->tdb; $this->assertEqual($DB->get_field_sql( - "SELECT " . $DB->sql_position("'ood'", "'Moodle'") . $DB->sql_null_from_clause()), 2); + "SELECT " . $DB->sql_position("'ood'", "'Moodle'") . $DB->sql_null_from_clause()), 2); $this->assertEqual($DB->get_field_sql( - "SELECT " . $DB->sql_position("'Oracle'", "'Moodle'") . $DB->sql_null_from_clause()), 0); + "SELECT " . $DB->sql_position("'Oracle'", "'Moodle'") . $DB->sql_null_from_clause()), 0); } } @@ -1376,8 +1423,10 @@ class moodle_database_for_testing extends moodle_database { public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0){} public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0){} public function get_fieldset_sql($sql, array $params=null){} - public function insert_record_raw($table, $params, $returnid=true, $bulk=false){} + public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false){} public function insert_record($table, $dataobject, $returnid=true, $bulk=false){} + public function import_record($table, $dataobject){} + public function reset_sequence($table){} public function update_record_raw($table, $params, $bulk=false){} public function update_record($table, $dataobject, $bulk=false){} public function set_field_select($table, $newfield, $newvalue, $select, array $params=null){}