Как получить ошибку SQLite в PHP/Laravel, когда цитируемый столбец не существует

Что касается моего предыдущего вопроса, я узнал, что из-за допущенной мной ошибки Laravel генерирует неправильный SQL-запрос:

select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "company_id" = '9c54986f-8284-4da9-b826-c7a723de279b'

Проблема здесь в том, что company_id не существует в companies; однако запрос не генерирует ошибку при запуске, он просто не возвращает результата.

Полагаю, проблема здесь в том, что "company_id" рассматривается как литерал, а не как ссылка на столбец; если я удалю кавычки, я получу правильную ошибку:

Error: in prepare, no such column: company_id (1)

Я также получаю правильную ошибку, если добавляю префикс таблицы к имени столбца:

sqlite> select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "companies"."compa
ny_id" = '9c54986f-8284-4da9-b826-c7a723de279b';
Error: in prepare, no such column: companies.company_id (1)

Есть ли способ решить эту проблему, воздействуя на конфигурацию Laravel или SQLite? Я не могу изменить способ генерации запросов, поскольку они генерируются самой платформой.

Кроме того, я НЕ спрашиваю, почему этот конкретный запрос ведет себя именно так, это мне уже было ясно.

Фрагмент and "company_id" = '9c54986f-8284-4da9-b826-c7a723de279b' генерируется глобальной областью видимости, реализованной следующим образом:

abstract readonly class UnlessAuthorizedScope implements Scope {
    public function __construct(
        private   string  $modelField,
        protected ?string $authorizingPermission,
        private   string  $userField,
    ) {}

    public function apply(Builder $builder, Model $model): void {
        if (Auth::hasUser()) {
            $user = Auth::user();

            if (
                !$this->authorizingPermission
                || !$user?->can($this->authorizingPermission)
            ) {
                $builder->where(
                    $this->modelField,
                    $user?->{$this->userField}
                );
            }
        }
    }
}

который затем реализуется в:

readonly class CurrentCompanyScope extends UnlessAuthorizedScope {
    public function __construct(?string $authorizingPermission = null, ?string $modelField = null) {
        parent::__construct(
            $modelField ?? "company_id",
            $authorizingPermission,
            "company_id"
        );
    }
}

и, наконец, используется как:

class Company extends Model {
    protected static function booted(): void {
        parent::booted();
        static::addGlobalScope(new CurrentCompanyScope(
            CompanyPermission::ViewAll->value,
            // the error was here, instead of specifying "id", I kept the default "company_id" value
        ));
    }
}
the error was here ... так вы уже исправили проблему в своем коде Laravel?
Tim Biegeleisen 20.06.2024 12:27

Да, в этом конкретном случае, но мне также хотелось бы найти способ сделать это так, чтобы он выдавал мне ошибку, а не молча выходил из строя, если я снова совершу подобную ошибку.

Matteo Tassinari 20.06.2024 12:29

Кстати, Laravel, скорее всего, заключает столбцы в двойные кавычки на случай, если вы используете ключевое слово в качестве идентификатора. Поэтому, если Laravel перестанет это делать, хотя это решит вашу проблему, это может создать другие проблемы.

Tim Biegeleisen 20.06.2024 12:35

Да, я так и предполагал, но мне хотелось бы знать, есть ли решение этой проблемы без потери функциональности фреймворка... У меня просто возникла идея, которую я мог бы попробовать.

Matteo Tassinari 20.06.2024 12:39

Примечание. Я отменил ваш вопрос, недавнее изменение которого сделало мой ответ недействительным. Не меняйте свой вопрос, включив в него мой ответ.

Tim Biegeleisen 20.06.2024 12:42

Я понятия не имею, о чем вы говорите, поскольку я просто показал, КАК был сгенерирован запрос; Кроме того, из-за вашего неправильного редактирования мой собственный ответ потерял контекст, поэтому я восстановлю его.

Matteo Tassinari 20.06.2024 12:45
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
8
6
168
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, что здесь происходит то, что "company_id" интерпретируется как строковый литерал, а не как столбец. Имейте в виду, что SQLite принимает строковые литералы как в одинарных, так и в двойных кавычках. Судя по всему, эвристика SQLite для интерпретации строк в двойных кавычках такова:

  • Сначала проверьте, можно ли сопоставить строку с каким-либо идентификатором базы данных (например, столбцом).
  • Если это не удалось, тогда обрабатывайте строку как строковый литерал.

Вот ваш запрос еще раз в формате:

SELECT *
FROM "companies"
WHERE
    "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' AND
    "companies"."deleted_at" IS NULL AND
    "company_id" = '9c54986f-8284-4da9-b826-c7a723de279b';

Поскольку столбец company_id не существует, этот запрос интерпретируется как:

SELECT *
FROM "companies"
WHERE
    "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' AND
    "companies"."deleted_at" IS NULL AND
    'company_id' = '9c54986f-8284-4da9-b826-c7a723de279b';

Конечно, сравнение строк в последнем члене предложения WHERE никогда не будет истинным, поэтому никакие записи не возвращаются.

Нет необходимости заключать столбцы в двойные кавычки, поэтому не используйте их, чтобы избежать проблем такого рода.

RE There is no need for your columns to be in double quotes, so don't use them, to avoid this sort of problem.: как сказано в моем вопросе, я не могу изменить способ генерации запросов, поскольку они генерируются самой платформой.

Matteo Tassinari 20.06.2024 12:11

@MatteoTassinari Тогда фреймворк не имеет смысла, если он добавляет случайные столбцы в ваше предложение WHERE. Более вероятно, что ваш код Laravel каким-то образом ссылается на столбец company_id.

Tim Biegeleisen 20.06.2024 12:15

@MatteoTassinari Ну, в любом случае, ваш вопрос был бы намного понятнее, если бы он содержал код, сгенерировавший ваш запрос.

KIKO Software 20.06.2024 12:16

Примечание. Это был ответ на исходный вопрос. Впоследствии пользователь изменил вопрос, включив в него мой ответ.

Tim Biegeleisen 20.06.2024 12:49

Не могли бы вы объяснить, что вы подразумеваете под «включением моего ответа»? То есть я не понимаю, какие части Вашего ответа я бы "включил"? На самом деле, это не риторика, я хотел бы понять, сделал ли я что-то не так, а английский не является моим основным языком, поэтому в этом моя трудность.

Matteo Tassinari 20.06.2024 12:56

@TimBiegeleisen Я перечитал ваш ответ, и если вы чувствуете, что я «включил» ту часть, где вы это говорите What I think is happening here is that "company_id" is being interpreted as a string literal, rather than a column., я хотел бы отметить, что такой вывод уже был в моей самой первой редакции; кроме этого, остальная часть вашего ответа никоим образом не присутствует в моем вопросе.

Matteo Tassinari 20.06.2024 13:03

RE: Then the framework makes little sense if it adds random columns to your WHERE clause., я никогда не утверждал, что это «случайно», я знаю, почему этот фрагмент запроса был там, и я исправил источник этой конкретной ошибки, которая не является сутью вопроса.

Matteo Tassinari 20.06.2024 19:22
Ответ принят как подходящий

Мне удалось решить эту проблему, изменив реализацию моей базовой глобальной области:

abstract readonly class UnlessAuthorizedScope implements Scope {
    public function __construct(
        private   string  $modelField,
        protected ?string $authorizingPermission,
        private   string  $userField,
    ) {}

    public function apply(Builder $builder, Model $model): void {
        if (Auth::hasUser()) {
            $user = Auth::user();

            if (
                !$this->authorizingPermission
                || !$user?->can($this->authorizingPermission)
            ) {
                $builder->where(
                    $model->getTable().".".$this->modelField, // changed here
                    $user?->{$this->userField}
                );
            }
        }
    }
}

добавив явный префикс таблицы к имени поля, он сохранялся в запросе, сгенерированном платформой, который затем стал:

select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "companies"."compa
ny_id" = '9c54986f-8284-4da9-b826-c7a723de279b'

и этот новый запрос сгенерировал правильное исключение, которое было перехвачено платформой и отображено на странице.

Другие вопросы по теме