13.2. Краткий курс

13.2.1. Сборка приложения

Чтобы построить приложение работающее с каким-нибудь SQL сервером, необходимо установить соответствующий пакет clip-<rdbms>. На данный момент доступны пакеты для следующих СУБД:

clip-postgres

PostgreSQL by (c) The PostgreSQL Global Development Group http://www.postgresql.org

clip-mysql

MySQL http://www.mysql.com

clip-oracle

Oracle 8i by (c) Oracle Corporation http://www.oracle.com

clip-odbc

ODBC driver manager http://www.microsoft.com

clip-interbase

Interbase/Firebird by (c) Borland/Inprise http://www.interbase.com

clip-dbtcp

Прокси сервер DBTCP для использования источников данных ODBC http://www.fastflow.it/dbftp

Установив пакет для выбранного SQL сервера можно собирать приложение примерно так:

bash$ clip -e test.prg -lclip-mysql

13.2.2. Шаг за шагом

Прежде всего приложение должно произвести подключение к серверу. Для этого предназначена функция ConnectNew() ConnectNew() - это конструктор класса TConnect, т.е. он возвращает объект класса TConnect. Этот объект может использоваться для выполнения операторов SQL; для выборки набора записей, возвращаемых запросом SELECT; а также для старта и завершения транзакций. Например:

conn := ConnectNew(...)  // obtain a connection
conn:Start()             // start a transaction

conn:Command("UPDATE emp SET name='Total' WHERE name='Rust'")
// next time, in pay office i'll say: "My name is Total"

conn:Rollback()          // just kidding :) cancel the change

Замечание

Одновременно может быть произведено несколько несколько подключений. Более того, возможно одновременное подключение к нескольким различным серверам.

В операторах и запросах SQL могут использоваться параметры. Именам параметров в строке, содержащей оператор или запрос, должен предшествовать символ ':'. Значения параметров передаются в двумерном массиве, одна строка на параметр. Первый столбец каждой строки должен содержать имя параметра, второй - его значение. Например:

conn:Command("UPDATE emp SET fname=:fname,lname=:lname",;
	{{"fname","John"},{"lname","Smith"}})

Для получения набора записей - результата запроса SELECT используется метод класса TConnect CreateRowset(). Он возвращает объект класса TRowset. Например:

rs := conn:CreateRowset("SELECT * FROM emp WHERE fname=:fname",{{"fname","John"}})
rs:Browse()    // simple BROWSE for TRowset

Несколько методов класса TRowset позволяют производить навигацию по наборузаписей: Bof(), Eof(), Skip(), Goto(), GoTop(), GoBottom(), Lastrec(), Recno().

Две функции предназначены для чтения/записи текущей записи набора: Read() и Write(). Read() возвращает объект с такой же структурой, что и структура записи. Например:

rs := conn:CreateRowset("SELECT fname,lname FROM emp")
? rs:Recno(), rs:Read() // 1 {FNAME: John, LNAME: Smith}

Метод Write() получает в качестве параметра объект с несколькими атрибутами, и устанавливает значения соответствующих полей. Например:

? rs:Read()            // {FNAME: John, LNAME: Smith}
obj := map()
obj:fname := "Robert"
obj:salary := 10000
rs:Write(obj)
? rs:Read()            // {FNAME: Robert, LNAME: Smith}

Можно добавлять записи к набору ((Append()) и удалять (Delete()). Append() получает параметр - объект. Например:

rs := conn:CreateRowset("SELECT fname,lname FROM emp")
? rs:Lastrec() // 100
obj := map()
obj:fname := "Homer"
obj:lname := "Simpson"
rs:Append(obj)
? rs:Lastrec() // 101
? rs:Read()    // {FNAME: Homer, LNAME: Simpson}
rs:Delete()
? rs:Lastrec() // 100

Замечание

Все изменения набора записей, производимые методами Write(), Append(), Delete() - локальны. Но конструктору TRowset могут быть переданы три дополнительных параметра (<cInsertSQL>, <cDeleteSQL>,<cUpdateSQL>). Если передан, оператор <cInsertSQL> будет неявно выполнен на сервере методом Append(). Точно так же <cDeleteSQL> и <cUpdateSQL> будут выполнены методами Delete() и Write(). В случае Write() и Delete() в наборе должно присутствовать поле с уникальными идентификаторами записей. См. подробности о путях решения этой проблемы драйверами разных СУБД. Например:

rs := conn:CreateRowset("SELECT rowid,fname,lname FROM emp",,;
	"INSERT INTO emp values (:fname,:lname)",;
	"DELETE FROM emp WHERE rowid=:rowid",;
	"UPDATE emp SET fname=:fname,lname=:lname WHERE rowid=:rowid")

В случаях когда количество возвращаемых записей невозможно предугадать будут полезны параметры <bEval> и <nEvery>. Блок кода <bEval> исполняется в процессе выборки через каждые <nEvery> записи. Если он возвращает .F. - процесс прекращается. Таким образом можно сделать индикатор процесса для больших результирующих наборов и прекращать выборку по требованию пользователя. Нижеследующий пример печатает точку через каждые 100 загруженных записей и может быть остановлен нажатием ESC.

rs := conn:CreateRowset("SELECT * FROM hugetable",,,,,,,,,,;
	{|| qqout("."), inkey() != K_ESC},100)

В подобных же случаях (когда невозможно предугадать размер результирующего набора) было бы полезно иметь возможность не загружать все записи сразу, а только тогда когда они потребуются. Для этого предназначен параметр <lNoFetch>. Если этот параметр .T., CreateRowset() завершается сразу после выполнения оператора SQL сервером, без загрузки записей. Загрузка производится позже, в процессе навигации по набору записей. Но в этом случае невозможно сразу определить количество выбранных записей, до тех пор пока остается хотя бы одна незагруженная запись. Для довыборки незагруженных записей существует функция TRowset:FetchAll(). Для определения количества загруженных на данный момент записей - TRowset:Fetched(). Например:

rs := conn:CreateRowset("SELECT * FROM hugetable",,,,,,,,,.T.)
rs:Gotop()
? rs:Fetched() // 1
? rs:Lastrec() // 0
for i:=1 to 100
	rs:Skip()
	? rs:Fetched() // 2,3,...,101
next
rs:FetchAll()
? rs:Lastrec() == rs:Fetched() // .T.

TRowset поддерживает так называемые "локальные индексы". "Локальный индекс" - это индекс, создаваемый на клиентской стороне и используемый для изменения логического порядка записей в наборе. Это практически то же самое, что и обычные индексы в RDD, но время их жизни ограничено временем жизни набора записей. Т.е. они располгаются в памяти клиентской машины, а не в файлах. Функция TRowset:CreateOrder() создает индекс с заданным именем. Функция TRowset:SetOrder() активирует индекс, после чего он начинает управлять логическим порядком записей. Например:

rs := conn:CreateRowset("SELECT fname,lname FROM emp")

// create an order 'Firstname' on the 'fname' field. Key length is 20 chars.
rs:CreateOrder("Firstname","fname",20)

// create an order 'Lastname' on the 'lname' field. Key length is 20 chars.
rs:CreateOrder("Lastname","lname",20)

// create an order 'Fullname' on the both 'fname' and 'lname' fields.
// Key length is 40 chars.
rs:CreateOrder("Fullname",{|rs| rs:GetValue("fname")+rs:GetValue("lname")},20)

rs:SetOrder("Firstname")
rs:Browse() // show rows sorted by first name

rs:SetOrder("Lastname")
rs:Browse() // show rows sorted by last name

rs:SetOrder("Fullname")
rs:Browse() // show rows sorted by fname and lname

Теперь перейдем к описанию функций и классов предназначенных для работы с SQL серверами.