Разработка веб-страниц с помощью google gears. Часть 2

February 22, 2008

Сегодня я продолжу рассказ о google gears. Технологии от google, которая позволяет изменить наш взгляд на веб-разработку и дает нам инструменты для создания веб-приложений наделенных чертами их более “серьезных” настольных собратьев. Созданное с помощью Gears приложение может работать без подключения к internet (все необходимые ресурсы будут храниться на локальной машине клиента), а после восстановления связи информация будет синхронизирована.

В прошлый раз я остановился на том, что показывал, как создать с помощью gears небольшое приложение “записная книжка”. Наверное, сегодня надо было бы завершить рассказ, показав как сохранить информацию внутри этой книжки (не на сервере, а на машине клиента) и как добавить функцию обмена этими сведениями с веб-сервером, но все же нет. Сегодня я смещу фокус рассказа с gears на sqlite. В своих статьях я стремлюсь рассказывать не просто об отдельной программе (языке программирования или библиотеке), а охватить весь спектр технологий, решающих некоторую практическую задачу. И даже если вы не станете использовать gears, то, скорее всего, полученные сегодня знания по sqlite помогут вам разобраться с другими технологиями, использующими sqlite (тот же adobe air), в разработке “портфельных приложений” или классических веб-сайтов.

Sqlite – довольно интересная СУБД: одна из целей ее разработки была в создании инструмента способного работать где угодно (не только сервера или настольные компьютеры, но и мобильные устройства), и со сколь угодно низкими затратами на ресурсы. Sqlite – встраиваемая СУБД. Это значит, что в отличие от семейства клиент-серверных СУБД (mysql, postgres, oracle), у sqlite нет какого-то отдельного процесса (сервера), который был бы запущен на специальном компьютере и ждал бы запросов на обслуживание от множества клиентов. SQlite представляет собой библиотеку dll, которую можно интегрировать в любое приложение (в настоящее время есть интерфейсы для доступа к sqlite из таких языков как: c++, php, perl, java, .net, ruby, delphi). Следовательно, Sqlite не рекомендуется использовать в тех ситуациях, когда база данных должна быть одновременно доступна для использования несколькими клиентами (даже на одной машине). Дело в том, что хоть чтение из базы данных могут выполнять сразу любое количество клиентов, но вот операция записи является монопольной. А значит, что для приложений, где количество операций “сохранить изменения” превалирует над “прочитать информацию”, sqlite будет мало полезна. Естественно, что семейство встраиваемых СУБД состоит не только из sqlite. Фактически все “большие и сложные” СУБД (mysql, mssql, oracle) имеют меньших братьев ориентированных на работу с меньшими нагрузками, не требующих сложных процедур установки и настройки (просто скопируй пару библиотек), способных работать на мобильных устройствах. Придут ли эти решения на рынок веб-приложений сказать трудно. Сейчас фокус интереса крупных разработчиков направлен на sqlite. Я говорю про google с gears, adobe с air, mozilla с firefox – все эти компании используют в своих продуктах sqlite. Одним из ключевых факторов успеха является то, что sqllite бесплатен и открыт для изменений всеми (лицензия public domain). Для лучшего понимания идеологии sqlite и направления его развития я настоятельно советую найти журнал linux format (он есть в свободном доступе в Интернет и даже на русском) за декабрь 2005 г., там было опубликовано интервью с создателем sqllite Ричардом Хиппом.

К сожалению, хорошей русскоязычной документации по sqlite очень мало: энтузиазма переводчиков (http://sb-news.net/sqlite.php) хватило не надолго. Если вам интересен вопрос связи php и sqlite, то наверняка найдете полезное для себя на странице http://www.phpclub.ru/detail/article/sqlight_intro или сайте http://www.codenet.ru/db/other/sqlite/. Для экспериментов с sqlite вовсе не обязательно писать код javascript с помощью gears. На сайте разработчиков библиотеки (http://www.sqlite.org/download.html) можно найти несколько вариантов поставки sqllite. Это может быть dll или so-библиотеки для встраивания движка базы в другое приложение, исходные коды библиотеки и, наиболее ценный для нас вариант, это загрузить архив с одной единственной утилитой sqlite3.exe. Это командная консоль sqlite, в ней вы можете писать команды sql и тут же получать ответ. Для тех, кого пугает черный экран с мигающим курсором и хочет использовать графическую оболочку, могу порекомендовать http://sqlitebrowser.sourceforge.net/ или http://www.sqlmaestro.com. Я настоятельно советую обзавестись каким-либо графическим менеджером СУБД: при знакомстве с sqlite вас ждет несколько сногшибающих новостей, пережить которые в одной консоли будет тяжело.

Хотя sqlite отлична от своих более серьезных родственников, но концепция у них одна и та же – реляционная. Это значит, что база данных (файл с расширением db) содержит описания таблиц, их индексы и, собственно, хранимую информацию. При запуске консоли sqlite.exe необходимо указать как параметр командной строки имя файла базы данных, с которым вы хотите работать. Если такого файла нет, то он будет создан (см. рис. 1).



Естественно, что gears никогда бы не позволят веб-приложению работать с файловой системой компьютера клиента и указать каталог, где будет создан файл базы данных. Так, когда вы вызываете gears функцию db.open('имя_базы_данных'), то в “секретном месте” создается файл с именем имя_базы_данных#database. Естественно, что gears заботятся о том, чтобы не возник конфликт, если пара веб-сайтов попробуют создать базу данных с одинаковым именем. Правда есть и плохая новость: если вы заходите на один сайт с помощью firefox и с помощью internet explorer, то созданные ими базы данных (одноименные) все равно будут считаться разными. Остается надеяться, что по мере развития gears эта проблема будет решена.

Команды, которые вы можете выполнять, делятся на две категории: стандартные sql-запросы и специфически команды, именно, sqlite (они начинаются с точки и в основном служат для того, чтобы посмотреть список таблиц в базе и других административных задач). Что касается поддержки sql, то нам придется ограничиться стандартом SQL92. В ходе дальнейшего чтения вам может показаться, что sqlite – это что-то очень “сырое” и “недоделанное”. На самом деле, выбор функциональности sqlite строго обоснован и ее нельзя сравнивать, например, с ранними версиями mysql (там тоже не поддерживались многие из определенных стандартом SQL функций). Начнем с самого простого: создания таблиц. И тут нас ждет первый сюрприз. В sqlite нет различия между типами данных. Наверняка, вы привыкли, что при создании таблицы (еще с самых древних времен) было необходимо сказать какие в ней должны быть поля, какой тип данных в этих полях мог храниться (строки, числа) и даже размеры этих типов (например, строка, но размером не более 100 символов). В sqlite всего этого нет. В версии 2.0 все типы данных воспринимались как строки. В версии 3 произошли изменения и были выделены четыре “ненастоящих” типа данных: целые и вещественные числа, строки, двоичные данные (BLOB). Я назвал эти типы данных “ненастоящими”, потому что могу поместить в поле, созданное как целое число, строку текста (и, похоже, так останется навсегда, Ричард Хипп достаточно четко выразился в этом плане). Вот простой пример, создающий таблицу для хранения сведений о людях (ввод команды должен завершаться символом точки с запятой):
  1. CREATE TABLE users (id, fio, age, sex);
Хоть ни одно поле не имеет указания на тип данных, пример работает. С другой стороны, если вы воспользуетесь каким либо визуальным редактором sql, то можете заметить, что мастер создания таблицы предлагает вам выбрать для полей тип из падающего списка (на рис. 2 показано создание таблицы с помощью SqliteMaestro). И здесь нет никакого противоречия. Дело в том, что сама идея отказаться от типов данных настолько выбивает из колеи программистов (и меня в том числе), что разработчики SqliteMaestro решили спрятать эти возможности подальше; и вы никак не сможете в графической оболочке ввести в некоторое поле, помеченное при создании как число, строку. Но если вы откроете консоль sqlite.exe, то карточный домик иллюзии наличия типов данных разрушится в мгновение. Если вам более привычно, то можете при создании таблицы указать тип создаваемых полей, но не надейтесь на то, что sqlite будет контролировать вводимые вами данные на предмет соответствия их этим типам данных.


  1. CREATE TABLE users2 (id int, fio varchar(100), birthday date, sex varchar(1));
Я все равно смогу вносить в поле, помеченное как date, произвольные строки текста. На самом деле в sqlite есть “хитрое понятие” называемое “manifest typing”. Его идея в том, что тип данных ассоциируется с самим значением, а не с полем или переменной, в которой это значение хранится. Это похоже на разницу между языками программирования с динамической и статической типизацией. Если вы попытаетесь внести в некоторое поле значение равное цифре, то оно будет сохранено как цифра. На стадии сравнения переменных или полей таблицы между собой, решение будет принято на основе значений, которые хранятся в этих полях. При создании таблицы не следует давать ей имя, начинающееся со слова "sqlite_" – оно является зарезервированным. Создаваемые таблицы могут быть как persistent (сохраняющими свои значения между несколькими сеансами sqlite в файле), так и temporary. В этом случае, если несколько пользователей одновременно работающих с одной и той же базой sqlite, создадут temporary таблицы (путь даже и одноименные), то эти таблицы будут независимы для каждого из подключений и будут автоматически уничтожены как только соединение с БД будет прервано. Вот пример создания временной таблицы (отличие от предыдущего примера только в ключевом слове “temporary”):
  1. CREATE TEMPORARY TABLE users7(fio, age, sex);
Создавая таблицу в любой другой СУБД, вы часто использовали “ограничения” накладываемые на значения полей (unique, check, триггеры). Посмотрим, что же уцелело из этого набора в sqlite? Уцелело все. Во-первых, сохранилась концепция первичного ключа (такого поля или набора полей, которые гарантированно уникальны для любых записей в таблице). Если поле при создании помечено как primary key, то любая попытка внести в него дублирующиеся значения будет неудачна.
  1. sqlite> CREATE TABLE x1 (i int PRIMARY KEY);
  2. sqlite> INSERT INTO x1 VALUES (1);
  3. sqlite> INSERT INTO x1 VALUES (2);
  4. sqlite> INSERT INTO x1 VALUES (2);
  5. SQL error: COLUMN i IS NOT UNIQUE
И даже есть возможность сказать, что значение первичного ключа будет назначаться не нами, а самой СУБД (старые добрые identity и auto_increment). Обратите внимание в следующем примере на то, что поле первичного ключа объявляется как INTEGER (не INT).
  1. sqlite> CREATE TABLE table_c (id INT PRIMARY KEY autoincrement, fio varchar(100));
  2. SQL error: AUTOINCREMENT IS only allowed ON an INTEGER PRIMARY KEY
  3. sqlite> CREATE TABLE table_d (id INTEGER PRIMARY KEY autoincrement, fio varchar(100));
  4. sqlite> INSERT INTO table_d (fio) VALUES ('Bill');
  5. sqlite> INSERT INTO table_d (fio) VALUES ('Mary');
  6. sqlite> SELECT * FROM table_d;
  7. 1|Bill
  8. 2|Mary
В том случае, если при вставке записи мы не укажем значения некоторого поля, то оно получит значение по умолчанию (DEFAULT). Этим значением может быть не только строка или число, но и ключевые слова: CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP – это соответственно: текущее время, текущая дата, текущая дата и время.
  1. sqlite> CREATE TABLE users5 (fio, now DEFAULT current_timestamp);
  2. sqlite> INSERT INTO users5 (fio) VALUES ('mary');
  3. sqlite> SELECT * FROM users5;
  4. mary|2008-01-15 14:26:47
Для того чтобы созданная нами таблица работала быстро, нам нужны индексы. Sqlite позволяет нам создать две их разновидности (кроме первичных): обычные и уникальные. Если обычные индексы только ускоряют операции с данными (зависящие от полей по которым построены индексы), то уникальные индексы запретят вам дублировать значения привязанных к ним полей. Есть два момента, когда вы можете создать индекс: либо на стадии определения таблицы, либо позже. Следующий пример показывает, как назначить полю fio уникальный индекс:
  1. CREATE TABLE users4 (id integer PRIMARY KEY, fio varchar(100) UNIQUE);
  2. INSERT INTO users4 (fio) VALUES ('mary');
  3. INSERT INTO users4 (fio) VALUES ('mary');
  4. SQL error: COLUMN fio IS NOT UNIQUE
Или после того как таблица была создана, вы можете добавить к ней как обычный, так и уникальный индекс. Важно при создании индекса назначить ему имя (в примере это idx_fio, unq_fio):
  1. CREATE INDEX idx_fio ON users(fio); создаем обычный индекс
  2. sqlite> CREATE UNIQUE INDEX unq_fio ON users(fio); и пример с уникальным индексом
  3. sqlite> INSERT INTO users (fio) VALUES ('Mark');
  4. sqlite> INSERT INTO users (fio) VALUES ('Mark');
  5. SQL error: COLUMN fio IS NOT UNIQUE
Индексы можно удалять, используя для этого команду “drop index имя_индекса”. Не важно уникальный это индекс, или обычный, главное, что бы они были созданы помощью “create index”.

Создавая таблицу, вы можете наложить ограничения на значения полей (например, величина зарплаты сотрудника должна быть в диапазоне от 0 до 1000$). Для этого используются CHECK ограничения. Есть два вида check: ограничения, накладываемые на одно поле, и ограничения, которые оперируют всеми полями в таблице одновременно. В следующем примере я создам таблицу с двумя “числовыми” полями. На первое из них будет наложено требование: значение поля должно быть более чем 100, для второго поля – значение менее 200, и третье ограничение говорит, что сумма этих двух полей не должна превосходить 150. Условия CHECK проверяются всегда (как при добавление новой записи, так и при редактирование существующей).
  1. sqlite> CREATE TABLE numbers (num_1 int CHECK(num_1 > 100), num_2 int CHECK (num_2 < 200), CHECK (num_1 + num_2 < 150));
  2. sqlite> INSERT INTO numbers VALUES (200,100);
  3. SQL error: constraint failed
  4. sqlite> INSERT INTO numbers VALUES (110,30); теперь ограничения CHECK не нарушены
  5. sqlite> UPDATE numbers SET num_1 = 400; пробуем обновить запись так, чтобы нарушить CHECK для всей таблицы
  6. SQL error: constraint failed
И третий способ контролировать ввод данных в таблицы – использовать триггеры. Триггер – это процедура, которая вызывается всякий раз, когда значение в таблице меняется (добавление, удаление или правка записей). Триггер может выполнять сложные проверки для вносимых данных, или выполнять изменения в смежных таблицах.

Очевидно, что созданная нами таблица может нуждаться в корректировке: мы можем захотеть добавить парочку полей, поменять их имена, удалить поле. Неприятный сюрприз в том, что команда ALTER (стандартная SQL инструкция правки таблицы) в sqlite реализована, мягко говоря, не полностью. Вы можете только переименовывать таблицы или добавлять к ним новые поля, например, так:
  1. ALTER TABLE users RENAME TO people; переименовали таблицу users в people
  2. ALTER TABLE users ADD cash float; добавили новое поле – зарплата человека
Если же вы хотите сделать что-то посложнее, то единственный путь – создать еще одну таблицу в соответствии с новыми пожеланиями, скопировать в нее данные из предыдущей и затем удалить старую таблицу. Это не сложно, но чтобы избежать глупых ошибок-опечаток лучше использовать, например, SqliteMaestro. Там мастер изменения структуры таблиц сам сгенерирует и выполнит все эти рутинные шаги.

Для удаления созданной таблицы используйте команду “drop table имя таблицы”.

Создав несколько таблиц, вы захотите просмотреть их список и то, из каких полей они состоят. В первом случае вам поможет команда “.tables ШАБЛОН”. Она выведет имена всех таблиц в текущей базе данных (или только тех, что соответствуют шаблону, если вы его укажите). Для того чтобы увидеть код, создавший таблицу, используйте команду “.schema ШАБЛОН”. В случае, если вы укажите значение шаблона, то будет выведен код DDL создающий ту таблицу, имя которой совпадает с этим шаблоном. Если же шаблон не указан, то будет выведен код создания для абсолютно всех таблиц. Наверняка пригодится и команда, которая создает полный дамп содержимого базы данных “.dump”. Не забудьте только, перед тем как выполнить дамп базы, перенаправить поток вывода во внешний файл, например, так: “.output имя_файла”. После чего вернуть вывод снова на экран поможет команда: “.output stdout”. Роль команды dump для переноса базы данных с одного компьютера на другой не столь же значительна как для mysql или postgres. С момента выпуска первой версии sqlite в 2000 году уже прошли десятки раз, когда менялся внутренний формат файла для хранения содержимого базы данных. Хотя в большинстве своем, каждая новая версия sqlite неплохо работает с файлами, созданными в предыдущей версии (но только в рамках одной “старшей версии”). Более того, формат хранения данных не зависит от разрядности компьютера (32 и 64 бита), порядка “старший байт-младший байт”. Фактически вы можете послать db-файл по электронной почте, будучи уверенным, что на другой стороне он “причитается” без проблем. Если же у вас возникнут проблемы, то используйте для переноса данных dump.

В следующий раз я продолжу и завершу рассказ о совместном использовании sqlite и google gears. Также я покажу, как использовать sqlite на стороне веб-сервера.