$this->parseTestQuestions(...);
в том случае, если перед этим закомментировать строку с unset().$stmt->executeQuery();
, ошибка тоже уходит.private function parseTests(Google_Service_Sheets $sheetsService, string $googleFileId): array
{
$range = "'Tests'!A2:B200";
/* Получаем данные из Google Spreadsheets */
$data = $sheetsService->spreadsheets_values->get($googleFileId, $range)->getValues();
$sql = "DELETE FROM `library_test`; ALTER TABLE `library_test` AUTO_INCREMENT=0;\n";
for ($i = 0; $i < count($data); $i++) {
/* Удаление строк с недостаточным числом ячеек либо отсутствующими id,
а также с пустыми названиями */
if ($data[$i][0] === '' || count($data[$i]) < 2 || !$data[$i][1]) {
array_splice($data, $i--, 1);
continue;
}
/* Вставка данных */
$sql .= "INSERT INTO `library_test` VALUES ({$data[$i][0]}, '{$data[$i][1]}', NOW());\n";
}
/* Если у нас нет строк, то ничего делать не нужно */
if ($sql != '') {
$stmt = $this->em->getConnection()->prepare($sql);
$stmt->executeQuery();
}
/* Без этой строки мы получим ошибку SQLSTATE[HY000]: General error: 2014 */
unset($stmt);
/* Импорт вопросов к тестам */
$this->parseTestQuestions($sheetsService, $googleFileId, $data);
/* Импорт ответов на вопросы к тестам */
$this->parseTestAnswers($sheetsService, $googleFileId, $data);
return $data;
}
<?php
class Test
{
private $conn;
public function __construct()
{
$this->conn = new PDO('mysql:dbname=test_db;host=localhost',
'login',
'password',
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")
);
}
public function insertData()
{
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
$st->execute();
/* Without this line we are getting an SQL error */
$st->closeCursor();
$this->insertDataInner1();
$this->insertDataInner2();
echo "OK";
}
public function insertDataInner1()
{
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test_a` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test_a`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
$st->execute();
}
public function insertDataInner2()
{
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test_b` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test_b`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
$st->execute();
}
public function selectData()
{
$st = $this->conn->prepare("SELECT * FROM `test` WHERE `id` < :id");
$st->execute(array('id' => '100'));
$array = $st->fetchAll(PDO::FETCH_ASSOC);
print_r($array);
}
}
$obj = new Test();
$obj->insertData();
/*
This way it works just fine without closing the cursor
$obj->insertDataInner1();
$obj->insertDataInner2();
*/
?>
запросы должны быть параметризованными
$conn = $this->em->getConnection();
$conn->query("DELETE FROM `library_test`");
$conn->query("ALTER TABLE `library_test` AUTO_INCREMENT=0");
$values = "";
$params = [];
for ($i = 0; $i < count($data); $i++) {
/* Удаление строк с недостаточным числом ячеек либо отсутствующими id,
а также с пустыми названиями */
/* Вставка данных */
$values .= $values ? "," : "";
$values .= "(?,?, NOW())";
array_push($params, $data[$i][0], $data[$i][1]);
}
/* Если у нас нет строк, то ничего делать не нужно */
if ($values) {
$sql = "INSERT INTO `library_test` VALUES $values";
$conn->prepare($sql)->execute($params);
}
Потому что API предназначено для выполнения запросов по одному
(val1, val2)
группы через запятую. $sql = "DELETE FROM `library_test`; ALTER TABLE `library_test` AUTO_INCREMENT=0;\n";
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test_a` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test_a`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
я всё равно не понимаю, почему оно не работает с несколькими запросами - ведь разделить по точке с запятой и отправить несколько запросов в БД - это несложная операция?
И если такой запрос работает, то это значит что данные соединяются с запросом на стороне клиента и в БД уже уходит обычный запрос.
Да не будет библиотека этого делать! К тому же просто разделить по точке с запятой - это гарантированно всё поломать, если указанный символ входит в состав строкового литерала. И что, теперь цеплять в библиотеку полноценный парсер?
Что написано, то и передаётся серверу в том виде, в каком написано. Это его парсеру разбираться.
В библиотеке PDO уже присутствует полноценный парсер SQL.
Vitsliputsli, но PDO-то поддерживает. Так что пользователь PDO может продолжать использовать prepare.
Парсер в PDO вообще ни разу не полноценный, а простая заменялка плейсхолдеров. В том числе для обеспечения поддержки эмуляции.
MySQL не поддерживает множественные запросы для подготовленных выражений
Парсер в PDO вообще ни разу не полноценный, а простая заменялка плейсхолдеров
Ну или попытаться написать этот код самому
Вам надо уяснить для себя, что защита от инъекций системы "проверенный человек вводит, мамой клянус!" - это детский лепет
Вам надо освежить память и вспомнить, что mysql_query вообще никогда не позволяла мультизапросы
mysqli_multi_query()
. что причина в вашей неспособности работать с документацией
А вы не знаете, почему был выбран разработчиками такой принцип?
Если один человек вводит, наверное он не станет вводить данные, которые могут привести к инъекции.
Что касается отсутствия поддержки множественных запросов с подготовленными выражениями, не скажу.
при существенном усложнении хранения и обработки
завтра может быть другой или кто-то получит его доступ
основное назначение подготовленного запроса - итеративные процедуры,
итеративные конструкции в MySQL появились только в 8 версии,
при существенном усложнении хранения и обработки
Ну и в крайнем случае, мне проще в цикле применить mysql_real_escape_string() или аналог, ну или просто проэкранировать все одиночные кавычки в строках (не факт, что этого достаточно, но существуенно усложнит атаку).
По мнению разработчиков, основное назначение подготовленного запроса - итеративные процедуры
почему мы не падаем с ошибкой прямо на этом запросе?
если бы мы упали, как мне объяснили на StackOverflow, это было бы отражено в результате.
Таким образом, проблема в том, что дефолтный режим запросов - unbuffered query
сам не закрывает соединение
А вместо этого мы свели вопрос к тому, что я плохо от инъекций защитил код.
Потому что там нет никакого подготовленного мульти запроса. Ни с плейсхолдерами, ни без.
как обычно, там соврали
с какого перепугу-то он будет закрывать
что чтобы получить ошибку, результат надо получить
ваши инсерты работают потому что в режиме эмуляции
потому что вы не запросили результаты своих запросов, и они остались ждать на сервере
которое пытаетесь формулировать через какой-то детский лепет про "параллельное выполнение"
по завершении работы функции всем её переменным делается unset
при этом куда проще выполнять запросы по одному: тогда не нужно проверять результат каждого вручную
чтобы ему разжевали, как работает сборка мусора при удалении переменной
$conn->prepare($sql)->execute($params1)->execute($params2);
Чувак забронзовел
проверять свои выкладки
2024-06-27T13:26:48.661692Z 8 Connect dev@localhost on test_db using TCP/IP
2024-06-27T13:26:48.662365Z 8 Query CREATE TABLE IF NOT EXISTS `test` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));
2024-06-27T13:26:48.671968Z 8 Query DELETE FROM `test`;
2024-06-27T13:26:48.674903Z 8 Query INSERT INTO `test` VALUES(1, 'A'), (2, 'B')
2024-06-27T13:26:48.676598Z 8 Query CREATE TABLE IF NOT EXISTS `test_a` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));
2024-06-27T13:26:48.679305Z 8 Query DELETE FROM `test_a`;
2024-06-27T13:26:48.681422Z 8 Query INSERT INTO `test_a` VALUES(1, 'A'), (2, 'B')
2024-06-27T13:26:48.683191Z 8 Query CREATE TABLE IF NOT EXISTS `test_b` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));
2024-06-27T13:26:48.684910Z 8 Query DELETE FROM `test_b`;
2024-06-27T13:26:48.686423Z 8 Query INSERT INTO `test_b` VALUES(1, 'A'), (2, 'B')
2024-06-27T13:26:48.687933Z 8 Quit
2024-06-27T13:27:22.843952Z 9 Connect root@localhost on using SSL/TLS
2024-06-27T13:27:22.844173Z 9 Query set autocommit=1
2024-06-27T13:27:22.844346Z 9 Query SELECT current_user()
2024-06-27T13:27:22.844551Z 9 Query SET CHARACTER SET utf8
2024-06-27T13:27:22.844694Z 9 Query SET NAMES utf8
2024-06-27T13:27:22.844829Z 9 Query SELECT CONNECTION_ID()
2024-06-27T13:27:22.845003Z 9 Query show character set where charset = 'utf8mb4'
2024-06-27T13:27:22.848080Z 9 Query SET NAMES 'utf8mb4'
2024-06-27T13:27:22.848289Z 9 Query SHOW SESSION STATUS LIKE 'Ssl_cipher'
2024-06-27T13:27:22.850108Z 9 Query USE `catquest_prod`
2024-06-27T13:27:22.850256Z 9 Query set autocommit=1
2024-06-27T13:27:22.850925Z 9 Query DROP TABLE `catquest_prod`.`test`, `catquest_prod`.`test_a`, `catquest_prod`.`test_b`
2024-06-27T13:28:42.488351Z 10 Connect dev@localhost on test_db using TCP/IP
2024-06-27T13:28:42.488678Z 10 Query CREATE TABLE IF NOT EXISTS `test` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));
2024-06-27T13:28:42.504759Z 10 Query DELETE FROM `test`;
2024-06-27T13:28:42.505702Z 10 Query INSERT INTO `test` VALUES(1, 'A'), (2, 'B')
2024-06-27T13:28:42.507564Z 10 Quit
Опыт №1: берем таракана, отрываем две ноги, свистим, таракан убегает.
Опыт №2: отрываем таракану четыре ноги, свистим, таракан убегает.
Опыт №3: отрываем таракану все ноги, свистим, таракан на месте.
Вывод: таракан без ног не слышит
объект, на который нет ссылок, уничтожается
Fourth is to never assign the result object to any PHP variable.
We ask MySQL to prepare the statement, save the allocated resources internally, execute the statement immediately and then discard the PDOStatement object that was received from prepare(). When the object is discarded, there is still one status message pending on the connection, but mysqlnd silently fetches it and discards it.
Вообще я стал замечать, что когнитивные способности у многих ровесников ухудшаются
мне кажется, что его нерегулярные подходы к этому вопросу имеют довольно банальное объяснение - он к нему возвращается сильно заложив за воротник
То есть "патч", который делает то, что и так есть
Что может быть проще, чем простой алгоритм: выполнил мультизапрос? Получи результаты всех последующих, и можешь двигаться дальше.
Нельзя открывать новый, не закрыв старый, сделав unset() переменной или прочитав его результаты, и всё тут.
Ну и я конечно дурак, не проверил кейс с одиночными подготовленными запросами без очистки переменных.
пул всех открытых соединений
не беспокоиться о получении ответа, когда он не нужен
Но это не самый простой патч, и потенциально он может привести к росту потребления памяти и не только. Поэтому вряд ли его когда-то реализуют.
Очистка/получение результатов нужны при использовании небуферизованных SELECT запросов
Никаких "соединений", и тем более "пула" тут нет
"не беспокоиться о получении ответа" и " I'm sure there is no error"- это детский лепет и профнепригодность. Если вам не важно, выполнился ли запрос, то его вообще выполнять не нужно было
вот здесь у вас проснулись проблески разума