Tietokannan käsittely ohjelmakoodin sisällä Xcode:ssa kannattaa
tehdä käyttäen apukirjastoja (SQL wrapper), joiden avulla
tietokantaoperaatiot on helppo ohjelmoida. Ei kannata käyttää
Macin omaa liitäntää SQL-lirjastoonsa. Olen käyttänyt kahta
tuunetuinta wrapperia: FMDB ja GRDB. Ensinmainittu on vanhempi ja
sen on ohjelmoitu Objective-C -kielellä. Siitä seuraa joitain
rajoituksia koodauksessa, esimerkiksi executeUpdate(..) on
käytettävissä vain rajoitetusti. GRDB on kokonaan ohjelmoitu
Swift-kielellä. Molemmat toimivat hyvin sekä Swift 5- että
SwiftUI-ympäristössä.
GRDB:n uusin versio 5.2.0 on valmistunut 29.11.2020, ja se on
ohjelmoitu kielellä Swift 5.2. Se löytyy täältä
Samassa paikassa on myös peruskäyttöohjeet, ja myös
yksityiskohtaisemmat käyttöohjeet. Se kannattaa asentaa
CocoaPods:n avulla.
TYÖVAIHEET:
1) Avaa terminaali ja anna komennot:
sudo gem
install cocoapods
pod setup --verbose
Tämä asentaa Macciisi cocoapods:in ja se on siellä myöhemmin
käytettävissä muita projektiasi varten.
2) Sinulla pitää olla aloitettuna projektisi, jossa käytät
SQLite:a. Sulje projektisi. Siirry terminaalissa kansioon, jossa
on projektitiedostosi, esimerkiksi GRDBtesti.xcodeproj
Anna sitten komento
pod init
Tämä tekee tiedoston nimeltä Podfile. Avaa tiedosto
tekstieditorilla (joka säilyttää utf-8) ja editoi se muotoon joka
vastaa ympäristöäsi:
platform :macos,
'11.0'
target 'GRDBtesti' do
use_frameworks!
pod 'GRDB.swift'
end
3) Seuraavaksi anna asennuskomento terminaalissa:
pod install
Projektin työkansio näyttää Finderissa nyt tällaiselta:

Nyt avaa projektisi kaksoisklikkaamalla xcworkspace-tiedostoa, ja
tästä lähtien projekti aina avataan siitä (Xcode muistaa).
Lisää jonkin kooditiedostoon alkuun lauseke import GRDB ja käännä
projektisi. Nyt ei pitäisi tulla ongelmia tämän lausekkeen
johdosta.
Alla muutamia esimerkkejä tavallisimpien SQL-lausekkeiden
käyttämisestä. Ne olen testannut SwiftUI-ympäristössä, mutta
toimivat myös puhtaassa Swift-ympäristössä. Lisää yksityiskohtia
ja selityksiä löytyy GRDB:n dokumentaatiosta. Olen huomannut, että
SwiftUI-ympäristössä kannattaa Xcode:ssa ahkerasti klikata "Clean
build folder", jos on muokannut ohjelmakoodia SQL-lausekkeiden
tienoilla. Tämän jälkeen Xcode ilmoittaa, että GRDB on hukassa,
mutta Build-klikkaus palauttaa taas tilanteen normaaliksi.
Lisäksi Xcode saattaa suositella tällaisia muutoksia:
"Build Settings - Automatically Select Architectures". Älä hyväksy
tällaisia.
Tietokannan avaaminen:
var dbg:
DatabaseQueue? = nildo
{
dbg = try DatabaseQueue(path:
polkuz) // polkuz on sqlite-tiedoston absoluuttinen
polku
}
catch
{
// virhe
}
Jos sinulla on do - catch -rakenne, jossa sisällä GRDB-toimintaa,
niin älä laita catch-osan sisään return-lauseketta. Ei se ole hyvä
tapa muulloinkaan.
Tietokantataulun luominen ja taulun päivittämiset:
do
{
try dbg.write
{
db in
try db.execute(sql: "CREATE TABLE
Kasvit (idi INTEGER PRIMARY KEY, nimi TEXT, harvi INT"
try db.execute(
sql: "INSERT INTO
Kasvit (nimi, harvi) VALUES (?, ?)",
arguments: ["Pujo",
2])
try db.execute(
sql: "UPDATE Kasvit
SET harvi = ? WHERE idi = ?",
arguments: [6,
4])
try db.execute(
literal:
"UPDATE Kasvit SET nimi = \(name) WHERE idi = \(id)")
}
}
catch
{
// virhe
}
Huomaa edellä literal-rakenne, joka muistuttaa merkkijonoa
String, mutta oikeasti se ei sitä ole. Kirjasto käsittelee sen
SQLLiteral-rakenteena välttyen siten SQL-injektiolta.
Edellä oleva dbg.write -rakenne toimii samalla transaktiona: jos
jokin osa pettää, niin mitään osaa ei lopulta tule suoritetuksi.
Transaktioon kuuluvat osat voivat olla myös loopin sisällä,
esimerkiksi:
do
{
try dbg!.write
{
db in
for i in 0..<pait.count
{
try
db.execute(literal: "INSERT INTO zb_apu (iKas) VALUES
(\(pait[i]))")
// TAI
ensin kootaan SQLLiteral, esim:
let
query = SQLLiteral(
sql: "INSERT INTO zc_apu (izKas, Knimi)
VALUES (?, ?)",
arguments: [kast[i], nimet[i]])
try
db.execute(literal: query)
}
}
}
catch
{
// VIRHE
}
DROP-komento voidaan toteuttaa mukavasti funktiolla:
do
{
try dbg!.write
{
db in
try db.drop(table: tauluz)
}
}
catch
{
// taulua ei ollut tai muu virhe
}
Tietoja tietokannasta haetaan seuraavilla tavallisilla tavoilla.
Käytettävissä on myös blobien käsittely (ei tässä demossa):
do
{
try dbg!.read
{
db in
let rows = try
Row.fetchCursor(db, sql: sqz)
while let row = try
rows.next()
{
let ii:
Int? = row["iPaikka"] // Int? voi olla
kannassa null
let ipa
= (ii == nil) ? 0 : ii!
var z:
String? = row["Vnimi"]
let
vnimiz = (z == nil) ? "" : z!
z =
row["Mika"]
let
mikaz = (z == nil) ? "" : z!
}
}
}
catch
{
// virhe
}
Jos SQL-lausekkeissasi on virhe, esimerkiksi siellä mainittua kenttää ei ole olemassa, niin useinkaan GRDB ei anna virheilmoitusta, vaan ohjelman toiminta vain pysähtyy. Xcode:ssa testatessa toiminta saattaa päättyä ilmoitukseen, että virhe on jonkun GRDB-osan Swift-tiedostossa. Joten sovelluksesi tulee testata huolella tällaisten virheiden varalta, ja huolehtia, että jokainen SQL-lauseke tulee testatessa ajettua.
Xcode:ssa koodia ei voi aina testata rivi kerrallaan. Esimerkiksi
SwiftUI:ssa edelläesitetyn fetchCursor-while-loopin sisällä on
käytettävä print()-lausekkeita, jos haluaa seurata mitä tapahtuu
missäkin. Tämä voi olla SwiftUI:n ongelma.
