Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Database / Driver / pgsql / Insert.php
1 <?php
2
3 namespace Drupal\Core\Database\Driver\pgsql;
4
5 use Drupal\Core\Database\Database;
6 use Drupal\Core\Database\Query\Insert as QueryInsert;
7
8 /**
9  * @ingroup database
10  * @{
11  */
12
13 /**
14  * PostgreSQL implementation of \Drupal\Core\Database\Query\Insert.
15  */
16 class Insert extends QueryInsert {
17
18   public function execute() {
19     if (!$this->preExecute()) {
20       return NULL;
21     }
22
23     $stmt = $this->connection->prepareQuery((string) $this);
24
25     // Fetch the list of blobs and sequences used on that table.
26     $table_information = $this->connection->schema()->queryTableInformation($this->table);
27
28     $max_placeholder = 0;
29     $blobs = [];
30     $blob_count = 0;
31     foreach ($this->insertValues as $insert_values) {
32       foreach ($this->insertFields as $idx => $field) {
33         if (isset($table_information->blob_fields[$field])) {
34           $blobs[$blob_count] = fopen('php://memory', 'a');
35           fwrite($blobs[$blob_count], $insert_values[$idx]);
36           rewind($blobs[$blob_count]);
37
38           $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], \PDO::PARAM_LOB);
39
40           // Pre-increment is faster in PHP than increment.
41           ++$blob_count;
42         }
43         else {
44           $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
45         }
46       }
47       // Check if values for a serial field has been passed.
48       if (!empty($table_information->serial_fields)) {
49         foreach ($table_information->serial_fields as $index => $serial_field) {
50           $serial_key = array_search($serial_field, $this->insertFields);
51           if ($serial_key !== FALSE) {
52             $serial_value = $insert_values[$serial_key];
53
54             // Force $last_insert_id to the specified value. This is only done
55             // if $index is 0.
56             if ($index == 0) {
57               $last_insert_id = $serial_value;
58             }
59             // Sequences must be greater than or equal to 1.
60             if ($serial_value === NULL || !$serial_value) {
61               $serial_value = 1;
62             }
63             // Set the sequence to the bigger value of either the passed
64             // value or the max value of the column. It can happen that another
65             // thread calls nextval() which could lead to a serial number being
66             // used twice. However, trying to insert a value into a serial
67             // column should only be done in very rare cases and is not thread
68             // safe by definition.
69             $this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", [':serial_value' => (int) $serial_value]);
70           }
71         }
72       }
73     }
74     if (!empty($this->fromQuery)) {
75       // bindParam stores only a reference to the variable that is followed when
76       // the statement is executed. We pass $arguments[$key] instead of $value
77       // because the second argument to bindParam is passed by reference and
78       // the foreach statement assigns the element to the existing reference.
79       $arguments = $this->fromQuery->getArguments();
80       foreach ($arguments as $key => $value) {
81         $stmt->bindParam($key, $arguments[$key]);
82       }
83     }
84
85     // PostgreSQL requires the table name to be specified explicitly
86     // when requesting the last insert ID, so we pass that in via
87     // the options array.
88     $options = $this->queryOptions;
89
90     if (!empty($table_information->sequences)) {
91       $options['sequence_name'] = $table_information->sequences[0];
92     }
93     // If there are no sequences then we can't get a last insert id.
94     elseif ($options['return'] == Database::RETURN_INSERT_ID) {
95       $options['return'] = Database::RETURN_NULL;
96     }
97
98     // Create a savepoint so we can rollback a failed query. This is so we can
99     // mimic MySQL and SQLite transactions which don't fail if a single query
100     // fails. This is important for tables that are created on demand. For
101     // example, \Drupal\Core\Cache\DatabaseBackend.
102     $this->connection->addSavepoint();
103     try {
104       // Only use the returned last_insert_id if it is not already set.
105       if (!empty($last_insert_id)) {
106         $this->connection->query($stmt, [], $options);
107       }
108       else {
109         $last_insert_id = $this->connection->query($stmt, [], $options);
110       }
111       $this->connection->releaseSavepoint();
112     }
113     catch (\Exception $e) {
114       $this->connection->rollbackSavepoint();
115       throw $e;
116     }
117
118     // Re-initialize the values array so that we can re-use this query.
119     $this->insertValues = [];
120
121     return $last_insert_id;
122   }
123
124   public function __toString() {
125     // Create a sanitized comment string to prepend to the query.
126     $comments = $this->connection->makeComment($this->comments);
127
128     // Default fields are always placed first for consistency.
129     $insert_fields = array_merge($this->defaultFields, $this->insertFields);
130
131     $insert_fields = array_map(function ($f) {
132       return $this->connection->escapeField($f);
133     }, $insert_fields);
134
135     // If we're selecting from a SelectQuery, finish building the query and
136     // pass it back, as any remaining options are irrelevant.
137     if (!empty($this->fromQuery)) {
138       $insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
139       return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
140     }
141
142     $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
143
144     $values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
145     $query .= implode(', ', $values);
146
147     return $query;
148   }
149
150 }