Энумсрач

The employer

Новичок
Автор оригинала: dimagolov
The employer

понятно, что однородных. но там и строка и число и дата может быть. или по-твоему надо все в кавычки брать, как DiMA? я не считаю это правильным, так как если нужно число, то дергать базу для запроса val = 'zhopa' не стоит даже если 'zhopa' корректно экранирована.
Никогда не было необходимости передавать в in что-то кроме чисел. Это вообще по большому счету костыль, который впервые понадобился полгода назад одному из наших разработчиков. Но тоже не вижу большой сложности найти решение для такого случая.


Автор оригинала: dimagolov
The employer, вообще-то triumvirat живой код представил, который он уже написал и как-то протестировал. сравнивать его с измышлениями не стоит, а из этой фразы видно, что ты придумываешь синтаксис и функциональность по ходу написания сообщений, что не есть айс.
Из этой фразы видно, что я сначала писал менее показательный пример (вставка одного поля), а потом решил написать более показательный (вставка двух полей разного типа). Откуда здесь можно сделать вывод который ты сделал - ума не приложу.

На всякий случай:
PHP:
svn log sm_db.class.php

------------------------------------------------------------------------
r144 | _ap | 2009-04-16 15:53:53 +0400 (Чт, 16 апр 2009) | 1 line


------------------------------------------------------------------------
r143 | _ap | 2009-04-16 11:05:01 +0400 (Чт, 16 апр 2009) | 1 line


------------------------------------------------------------------------
r136 | _ap | 2009-02-27 13:10:38 +0300 (Пт, 27 фев 2009) | 1 line


...

------------------------------------------------------------------------
r2 | ap | 2007-12-20 13:23:07 +0300 (Чт, 20 дек 2007) | 1 line


------------------------------------------------------------------------

PHP:
svn diff -r 136 sm_db.class.php

Index: sm_db.class.php
===================================================================
--- sm_db.class.php	(revision 136)
+++ sm_db.class.php	(working copy)
@@ -30,7 +30,7 @@
      * @access private
      * @var array
      */
-    private $valid_placeholders = array( "i", "d", "s", "b", "n", "t", "f" );
+    private $valid_placeholders = array( "i", "d", "s", "b", "n", "t", "f", "a" );
 
 
     /**
@@ -94,9 +94,15 @@
      *
      * @access public
      * @param array $params = array (
-     *                  "query" => value                 - запрос
-     *                , "binds" => array()               - массив для подстановки в запрос
-     *                , "limit" => value                 - значения для ограничения выборки  
+     *                  "query" => value      - запрос
+     *                , "binds" => array()    - массив для подстановки в запрос
+     *                , "limit" => value      - значения для ограничения выборки  
+     *                , "callback" => string  - имя функции, которой будет
+     *                                          передана для обработки
+     *                                          каждая строка получаемого рекордсета
+     *                                          Параметры - номер строки, сама строка
+     *                                          Результат - строка, которую нужно
+     *                                          положить в рекордсет
      *              )
      * @return array ( 
      *     "affected_rows" => value
@@ -155,7 +161,9 @@
 
         $this->translated_query = $query;
 
-        return $this->getResult( $query, $limit );
+        $callback = isset( $params["callback"] ) ? $params["callback"] : "";
+
+        return $this->getResult( $query, $limit, $callback );
 		
     } // function query()
 
@@ -282,15 +290,21 @@
      * @access private
      * @param string $query
      * @param string $limit - значения для ограничения выборки
+     * @param string $callback - имя функции, которой будет
+     *                           передана для обработки
+     *                           каждая строка получаемого рекордсета
+     *                           Параметры - номер строки, сама строка
+     *                           Результат - строка, которую нужно
+     *                           положить в рекордсет
      * @return array ( 
      *     "affected_rows" => value
      *   , "result"        => array()
      * )
      */
-    private function getResult( $query, $limit = null ) {
+    private function getResult( $query, $limit = null, $callback = "" ) {
 		
         try {
-            $res = $this->driver->getResult( $query, $limit );   
+            $res = $this->driver->getResult( $query, $limit, $callback );   
         } catch ( Exception $e ) {
             throw new sm_exception( $e->getMessage()."<br>SQL-query: ".$this->translated_query );
         }
@@ -452,6 +466,41 @@
     } // function placeholder_f()
 
 
+    /**
+     * Стандартный обработчик placeholder 'a'
+     *
+     * @access private
+     * @param (string|array) $bind = value1,value2,...,valueN || $bind = array(value1,value2,...,valueN)
+     * @return string - значения, которое можно использовать в IN (пример возврата: "('a','b')")
+     */
+    private function placeholder_a( $bind ) {
+
+        if (is_string($bind)) {
+            if ("" === $bind) {
+                $data = array();
+            } else {
+                $data = explode(",", $bind);
+            }
+        } else {
+            $data = $bind;
+        }
+
+        if (!is_array($data)) {
+            throw new sm_exception( "Incorrect type 'a': ".(string)$bind, 0 );
+        }
+
+        if (!count($data)) {
+            return "(null)";
+        }
+
+        foreach ($data as $k => $v) {
+            $data[$k] = "'" . $this->escape_string($v) . "'";
+        }
+
+        return "(" . join(",", $data) . ")";
+
+    } // function placeholder_a()
+
 } // class sm_db
 		
 ?>
\ No newline at end of file
Это часть нашей платформы разработки, которую мы потихонечку готовим к выдвижению в open source. Сейчас актуальная версия 1.1, открыта будет следующая версия - 2.0. Используется с июня 2007 года, но в конце того же года был сменен репозиторий.
 

DiMA

php.spb.ru
Команда форума
> [size=+1]Никогда не было необходимости передавать в in что-то кроме чисел.[/size] Это вообще по большому счету костыль, который впервые понадобился полгода назад одному из наших разработчиков.

Про поля ENUM не слышал? Нужно его использовать, а не числа, ведущие к потенциальным ошибкам. Про SET видимо тоже не слышал.

> или по-твоему надо все в кавычки брать, как DiMA? я не считаю это правильным

В чем проблема с кавычками? Послушаю с интересом об этой проблеме, если кто-то знает и обещаю исправиться :) Сам я думаю, что кавычки нужны всегда, хотя бы на том основании, что MYSQL возвращает числовые поля как строковый тип в пхп при чтении. Точно так же из $_REQUEST всегда приходят только строки.

Единственное исключение - NULL. Но это уже программер головой думает, когда пишет запрос. И если NULL/BOOL нужно воспринимать не как отстуствие переменной, а как пристутствие спец значения - поступает иным образом.

> "i", "d", "s", "b", "n", "t", "f"

Нужно использовать понятные строки, например целиком заимствовать названия типов из PHP или SQL, а не сочинять свои. С буквами "i" и "s" - понятно, можно сокращать. Остальное - бред.
 

The employer

Новичок
Автор оригинала: DiMA
> [size=+1]Никогда не было необходимости передавать в in что-то кроме чисел.[/size] Это вообще по большому счету костыль, который впервые понадобился полгода назад одному из наших разработчиков.

Чайник, про поля ENUM не слышал? Нужно его использовать, а не числа, ведущие к потенциальным ошибкам. Про SET видимо тоже не слышал.
Даже в приведенном мной примере есть намек - это имя ip_addr у параметра запроса. Применялось для поиска определенных IP в логе доступа (разумеется, в таблице IP хранятся как int). Но ты, конечно, ничего не понял.


Автор оригинала: DiMA
> "i", "d", "s", "b", "n", "t", "f"

Гамно. Нужно использовать понятные строки, например целиком заимствовать названия типов из PHP или SQL, а не сочинять свои. С буквами "i" и "s" - понятно, можно сокращать. Остальное - бред.
А это уже просто дело вкуса. И форма выражения своих вкусов, конечно.
 

DiMA

php.spb.ru
Команда форума
> Парсер, написанный на PHP будет работать медленно

Ага, парсилки запросов на пхп - полный тупняк. Необходимо использовать более быстрые расширения пхп.

> Но ты, конечно, ничего не понял.

~ Половина таблиц содержит ENUM. ~ Половина запросов оперирует полями ENUM. Поэтому ~ 25% всех запросов некого абстрактного проекта должно содержать IN со строками.
 

The employer

Новичок
Автор оригинала: DiMA
~ Половина таблиц содержит ENUM. ~ Половина запросов оперирует полями ENUM. Поэтому ~ 25% всех запросов некого абстрактного проекта должно содержать IN со строками.
Мой опыт говорит о том, что enum есть смысл делать не так часто, и вот почему: часто получается так, что значения, про которые на старте проекта говорилось что "возможен только вот такой набор значений и все" - дальше, с течением времени, потребуют либо вынесения их в справочник, либо даже включения их в иерархию объектов предметной области, с наличием подчиненных им объектов.

P.S. Особенно наглядно это проявляется в системах, где есть развитая система полномочий, когда доступ к производному объекту регламентируется в том числе правами доступа ко всем объектам-родителям. Скажем, доступ к договору между двумя контрагентами в факторинговой системе может зависеть от доступа к каждому из контрагентов, и доступа к статусу договора, который сам по себе - объект. Понятно почему статус договора должен быть объектом, или нет?
 

dimagolov

Новичок
The employer, enum имеет смысл делать очень часто, когда кол-во перечислимых значений в принципе не может превысить 256 и они используются только в одном месте (в обной таблице). отличный заменитель magic_numbers
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
перенес ваш энумсрач в SQL
та тема сама по себе интересна

-~{}~ 25.07.09 22:07:

Я использую enum часто - например, для указания статуса сущности.
Например, active/suspended/deleted - по сути константа.
Был рад появлению подобного типа в постгресе.

В where ... in(...) числа стоит использовать просто для производительности - сравнение строк по определению происходит дольше.
Но при выборке по каталогу могу использовать перечень имен категорий - сказать, что строки в in() редкость нельзя.
 

DiMA

php.spb.ru
Команда форума
> когда кол-во перечислимых значений в принципе не может превысить 256

мда, ребята, а это заразительно...

ENUM - это любое из 65535 значений. Этого числа хватит на 100 лет, если каждый день менять по 2 значения одного и того же поля ENUM.

SET - комбинация из 64 (+нулл и пусто).



> часто получается так, что значения, про которые на старте проекта говорилось что "возможен только вот такой набор значений и все" - дальше, с течением времени,
> потребуют либо вынесения их в справочник, либо даже включения их в иерархию объектов предметной области, с наличием подчиненных им объектов

ENUM как раз для этого и предназначен! Создаем поле один раз раз и на века жизни проекта.

Вот ведь упоротая домохозяйка какая... не может успокоиться, признать глупость и начать делать правильно...

> Ты не видел все проекты на свете, и никакого права делать такое сильное утверждение у тебя нет.

Специально поясняю элементарные вещи, описанные кратко grigori: ваще всегда и везде, за редким исключением, в любой таблице SQL должно существовать поле "Удалено". Смысл в том, что если данные удаляются, то мы просто ставим этот флажок "удалено", а все выборки всегда включают дополнительное условие (выбрать траляля, где Удалено не установлено). Командой DELETE не пользуемся. В проектах Васи Пупкина подобные детали с целью сохранить целостность не рассматриваются. А преимуществ много: защита от спама/взлома, логи всех данных (если нужно поднять историю), отсутствие необходимости лочить записи в транзакциях (проблемы, когда сотни параллельных потоков будут делать одно и тоже), проблемы с репликациями, выигрыш за счет нетронутых индексов, и прочие блага этого подхода. Так вот, чтобы это поле ввести, необходимо завести ENUM с двумя значениями "Активное", "Удаленное" (можно расширять, если задача требует). Можно завести и просто SMALLINT, но это числовой идиотизм, вернее по научному - антипаттерн "магические числа". Поэтому использовать ENUM нужно очень часто, практически в ЛЮБОЙ таблице. Ну, в 10% таблиц ради оптимизации или логики (таблица - денормализированный список других данных) активность действительно полей не нужна.

Нда... я уже вижу, как ты ща опять будешь усираться, доказывая, что "нормальные люди в реальной жизни" так не делают :) Мол диск у него переполниться, если ничего не удалять, и еще сто причин, тока бы отстоять свою кривую точку зрения :)
 

korchasa

LIMB infected
Автор оригинала: DiMA
...Можно завести и просто SMALLINT, но это числовой идиотизм, вернее по научному - антипаттерн "магические числа"...
0 и 1 не очень похожи на магические числа. И даже если это не 0 и 1, а еще и 3 4 5, то магические они только при просмотре содержимого базы, в коде они все равно должны быть обернуты в какие-то константы.
Так, что отличие ENUM'а от INT'а только в удобстве просмотра базы, защиты от опечатки на уровне БД, и необходимости обновления при добавлении нового значения.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Автор оригинала: korchasa
0 и 1 не очень похожи на магические числа. И даже если это не 0 и 1, а еще и 3 4 5, то магические они только при просмотре содержимого базы, в коде они все равно должны быть обернуты в какие-то константы.
Магическими они становятся, когда через 2 года начинаю похожий проект и читаю select ... from users where status>4.

Часто заказчик говорит "надо, чтобы пользователь мог залогиниться, но не мог пополнить счет".
Можно добавить поле bool, можно добавить новый статус.

Да, раньше в постгресе я писал
$sql = 'select ... from users where status in ('._Operator::USER_STATUS_ACTIVE.','._Operator::USER_STATUS_VERIFIED.')';

С энумом запрос пишется как select ... from users where status in ('active','verified').

почти то же самое, но и в базу заглянешь, и ... эстетичней что-ли
ради эстетики иногда пол-проекта переписываем, не так ли? :)
 

korchasa

LIMB infected
Автор оригинала: grigori
Да, раньше в постгресе я писал
$sql = 'select ... from users where status in ('._Operator::USER_STATUS_ACTIVE.','._Operator::USER_STATUS_VERIFIED.')';
Это я имел ввиду, когда говорил, что в коде это должно быть обернуто в константы

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

-~{}~ 26.07.09 17:26:

Хм, как-то странно "мусор" отделили.

Автор оригинала: DiMA
Вот ведь упоротая домохозяйка какая... не может успокоиться, признать глупость и начать делать правильно...
Это видимо адекватная часть?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
когда пишешь - не вопрос, а вот через пару лет ... начинает подташнивать :)
 

korchasa

LIMB infected
Автор оригинала: grigori
когда пишешь - не вопрос, а вот через пару лет ... начинает подташнивать :)
Если с фанатичным гневом, присутствующим выше, то может и на куски порвать, да. ИМХО, это больше дело привычки и внутренних стандартов.
 

The employer

Новичок
Автор оригинала: grigori
Магическими они становятся, когда через 2 года начинаю похожий проект и читаю select ... from users where status>4.

Часто заказчик говорит "надо, чтобы пользователь мог залогиниться, но не мог пополнить счет".
Можно добавить поле bool, можно добавить новый статус.
Решение зависит от кейса. Какие причины стоят за хотелкой "надо, чтобы пользователь мог залогиниться, но не мог пополнить счет"?

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

Техническим решением здесь enum может быть только в тривиальных случаях. На практике всегда лучше иметь справочник состояний, фиксирующий в том числе возможные переходы и возможные операции. Это резко упрощает программный код. А отсюда уже всего два шага до элементарного DSL.

Автор оригинала: grigori
Да, раньше в постгресе я писал
$sql = 'select ... from users where status in ('._Operator::USER_STATUS_ACTIVE.','._Operator::USER_STATUS_VERIFIED.')';

С энумом запрос пишется как select ... from users where status in ('active','verified').

почти то же самое, но и в базу заглянешь, и ... эстетичней что-ли
ради эстетики иногда пол-проекта переписываем, не так ли? :)
Да только вот посмотрев в базу ты так и не сможешь дать точный ответ на вопрос - а что же этот конкретный пользователь может делать. имея статус, например, 'varified'. Чтобы такой ответ дать, тебе придется "в уме" сложить логику приложения (тот самый 'where status in...', причем везде где такое встречается, с разными комбинациями в in), и то что ты видишь в БД.

Любое более-менее сложное приложение лучше снабжать собственным браузером объектов, потому что при достаточно развитых взаимосвязях "дизассемблирование" объектов путем прямого просмотра таблиц становится просто по времени неэффективным занятием.

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

И, более того, часто код вообще не должен содержать этих констант.

На твоем примере. Бизнес-кейс: "надо, чтобы пользователь мог залогиниться, но не мог пополнить счет".

Должно ли появление такого кейса оказать воздействие на обработчик формы логина? Нет, потому что используется метод login объекта user (если так, по-простому).

А на код, формирующий пользовательское меню - должен новый кейс оказать воздействие? Нет. Потому что вызывается метод createMenu объекта userMenu, в который передаются права текущего пользователя в виде его статуса (или роли, если по-взрослому).

А на что оказывает воздействие кейс? На появление нового статуса (или новой роли), на правила назначения этого статуса (или роли), на настройку взаимосвязей этого статуса или роли с прочими объектами системы (такими как возможные действия пользователя в этой роли и правила комбинирования ролей).

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

А учитывая, что статус (роль) пользователя - это полноценный объект предметной области, в системе предусмотрен полноценный механизм работы с этим объектом прямо в интерфейсе.

Таким образом, в нормально написанной системе, кейсы касающиеся изменения полномочий пользователей реализуются администраторами системы, а не программистами с enum наперевес.

P.S. Я понимаю, что ты решил удалить мое прошлое сообщение из-за резких высказываний в адрес одного гражданина. Но в данном случае с водой ты выплеснул и ребенка.

-~{}~ 26.07.09 18:18:

P.P.S. Кстати, в промышленных системах требование возможности внесения изменений в систему полномочий без необходимости вмешательства в код системы - обычно уже в ТЗ :)
 

korchasa

LIMB infected
Автор оригинала: The employer
P.P.S. Кстати, в промышленных системах требование возможности внесения изменений в систему полномочий без необходимости вмешательства в код системы - обычно уже в ТЗ :)
Это же только единичное применение. Вряд ли кто-то будет закладывать такие возможности, для каких нибудь простых фич, типа внутренних сообщений, или очереди исходящей почты.
 

The employer

Новичок
Автор оригинала: korchasa
Это же только единичное применение. Вряд ли кто-то будет закладывать такие возможности, для каких нибудь простых фич, типа внутренних сообщений, или очереди исходящей почты.
Будет. И для сообщений, и для документов (система динамически настраиваемых статусов - это как раз азы документооборота, без этого проблемы неизбежны и надежно гарантированы).
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
а кто-то здесь говорит о документообороте?

> справочник состояний, фиксирующий в том числе возможные переходы и возможные операции

>Это резко упрощает программный код.
Со скольки строк строк до скольки?

>Любое более-менее сложное
Предоставь критерий определения
* достаточной сложности приложения
* достаточности развития взаимосвязей
* нормального приложения (от ненормального)

>статус (роль) пользователя - это полноценный объект предметной области,
>в системе предусмотрен полноценный механизм работы с этим объектом прямо в интерфейсе.

Знаешь анекдот про верблюжонка с мамой в зоопарке?
"А зачем нам все эти навороты?"
Большинство пишет (условно) по 5 похожих магазинов за $1500 каждый, надевая верстку на коробку, а не ERP АвтоВАЗа.
Хочешь дописывать "объект предметной области" в вордпрес и track?

Кришна, +1 - ты был прав с самого начала ;)
 
Сверху