Sessiot, autentikointi ja relaatiotietokannat

Harjoitellaan sessioiden käyttöä WWW-sovelluksen tekemisessä. Lisätään sovellukseen autentikointi. Tutustutaan relaatiotietokantoihin.

Laskuri sessioilla

Etsi edellisessä pääteohjauksessa tekemäsi laskurisovellus. Korjataan laskuri toimimaan kunnolla sessioiden avulla.

Autentikointi

Käyttäjän kirjautuminen ja autentikointi järjestelmään tehdään sessioiden avulla seuraavalla tavalla:

  1. Täytyy ensimmäisenä luoda sivu jolla voi kirjautua sisään laskurisovellukseen. Tee uusi sivu (/kirjaudu) ja sijoita sivulle kirjautumislomake jolla kysytään tunnus ja salasana

    autentikointilomake

  2. Tarkista sivulla, että käyttäjätunnus on ties4080 ja salasanaksi on syötetty testi. Salasanan vertailu on tehtävä seuraavalla tavalla:
        import hashlib
        m = hashlib.sha512()
        avain = u"omasalainenavain"
        m.update(avain.encode("UTF-8"))
        m.update(salasana.encode("UTF-8"))
        if tunnus=="ties4080" and m.hexdigest() == "366e90b5fe29a9d9c1420afa334c4b19c4d63dcd200f424b7a9fe3328a352da5818fc03cffa463c2362db3535b612df4eb27df33d4720fbf592964571ad7572e":
            # jos kaikki ok niin asetetaan sessioon tieto kirjautumisesta ja ohjataan laskurisivulle
            session['kirjautunut'] = "ok"
            return redirect(url_for('laskuri'))
        # jos ei ollut oikea salasana niin pysytään kirjautumissivulla. 
        return render_template('kirjaudu.html')

    Selkokielistä salasanaa ei siis vertailla vaan salasanasta jollakin algoritmilla laskettua merkkijonoa. Testi-salasanaa vastaava merkkijono on tuotettu seuraavalla tavalla. Oikean salasanan vertailu pitää tehdä samalla tavalla jolloin voi verrata onko m.hexdigest-funktion palauttama merkkijono sama kuin haluttu.

    >>> import hashlib
    >>> m = hashlib.sha512()
    >>> m.update("omasalainenavain".encode("UTF-8"))
    >>> m.update("testi".encode("UTF-8"))
    >>> m.hexdigest()
    "366e90b5fe29a9d9c1420afa334c4b19c4d63dcd200f424b7a9fe3328a352da5818fc03cffa463c2362db3535b612df4eb27df33d4720fbf592964571ad7572e"
  3. Kokeile toimiiko kirjautuminen eli ohjaudutko laskurisivulle, jos syötät oikean salasanan
  4. Tee myös logout-sivu, joka poistaa sessiosta kirjautunut-avaimen, ja ohjaa sen jälkeen kirjaudu-sivulle
  5. Vielä tarvitaan oma decorator, jolla saadaan helposti yhdistettyä kirjautumisvaatimus halutuille sivuille. Vrt. app.routes. Lisää sivun alkuun:
    from functools import wraps
    Kirjoita seuraavanlainen funktio:
    def auth(f):
        ''' Tämä decorator hoitaa kirjautumisen tarkistamisen ja ohjaa tarvittaessa kirjautumissivulle
        '''
        @wraps(f)
        def decorated(*args, **kwargs):
            # tässä voisi olla monimutkaisempiakin tarkistuksia mutta yleensä tämä riittää
            if not 'kirjautunut' in session:
                return redirect(url_for('kirjaudu'))
            return f(*args, **kwargs)
        return decorated
  6. Kokeile nyt käyttää omaa @auth-decoratoria. Lisää se esim. laskuri-funktion eteen:
    @app.route('/laskuri', methods=['POST','GET']) 
    @auth
    def laskuri():
    
  7. Kokeile mennä selaimella laskurisivulle. Ohjaudutko kirjautumiseen? Jos et, niin muista poistaa vanha kirjautuminen logout-sivulla ja kokeile sen jälkeen uudelleen.
  8. mallivastaus ( oma.py, kirjaudu.html )
  9. mallivastaus v2 - 2024 ( kirjaudu.py, kirjaudu2.html )

Tietokannat

Harjoitellaan tietokantojen käyttämistä.

Mallivastaus: reseptit (Lähdekoodi, Template)

Katso ennen näiden tehtävien tekemistä luennot: Tietokannat ja Python ja Tietokannat ja python jatkoa.

Tietokantatehtävät voi tehdä myös ajamalla python-sovellusta suoraan halavassa/jalavassa komentoriviltä.

Muista myös Pythonin sqlite3-kirjaston dokumentaatio

Tietokannan luominen

Tehtävissä käytetään SQLite3-tietokantaa, jota voidaan hallinnoida suoraan komentoriviltä käynnistyvällä työkalulla. Samaan tapaan onnistuu myös hienompien tietokannan hallintajärjestelmien hallinnointi (PostgreSQL, MySQL). SQLite-tietokannan kanssa mahdollisesti vastaantulevista ongelmista on oma FAQ.

  1. Ota Puttylla SSH-yhteys halava.cc.jyu.fi:hin.
  2. Siirry public_html-kansioosi eli symbolisen linkin kautta W:-asemaa vastaavaan paikkaan. Luo tänne alihakemisto hidden ellet luonut sitä jo aiemmin.
  3. Kopioi itsellesi resepti.sql-tiedosto wget-komennolla:
    wget https://appro.mit.jyu.fi/ties4080/ohjaus/ohjaus3/resepti.sql

    Tietokannan rakenne:

    Reseptitietokannan rakenne
  4. Avaa hidden-kansiossa sqlite3 komennolla:
    sqlite3 resepti
  5. Luo tietokannan rakenne komennolla:
    .read resepti.sql

    SQLite lukee resepti.sql-tiedoston ja suorittaa sen sisältämät SQL-komennot. Jos SQLite kaatuu, käynnistä se uudelleen.

    SQLite luo nyt uuden resepti-tiedoston ja tekee siitä tietokannan. SQLite saattaa kaatua, mutta sen pitäisi silti luoda tietokanta. Kokeile käynnistämällä SQlite uudelleen ja kirjoittamalla .tables. Jos saat listauksen tietokannan tauluista niin kaikki on ok.

  6. Asetetaan tietokantaan WAL (Write-Ahead Logging) päälle:
    PRAGMA journal_mode = wal;
    Useimmat PRAGMA-komennoista täytyy antaa joka kerta sovelluksen tietokantayhteyden avaamisen yhteydessä, mutta WAL-moden asettaminen jää kertoasetuksella voimaan.
  7. Voit tutustua sqlite3-työkalun osaamiin komentoihin kirjoittamalla .help.
  8. Tutki tietokantasi rakennetta seuraavilla komennoilla: .tables, .schema resepti ja .schema ruokalaji.
  9. Tutki mitä taulut sisältävät yksinkertaisilla SQL-kyselyillä:
    SELECT * FROM Resepti;

    SELECT * FROM Ruokalaji;
  10. .quit-komennolla pääset takaisin unix-shelliin.
  11. Anna tietokantaan ja kansioon (hidden), jossa tietokantatiedosto sijaitsee, kaikille kirjoitusoikeus (chmod a+rw resepti ja chmod a+rwx ./), niin tietokantaan kohdistuvat muutosoperaatiot toimivat jatkossa myös WWW:n kautta. Näiden oikeuksien antaminen on selkeä tietoturvaongelma, mutta muuta vaihtoehtoa ei ole users.jyu.fi-palvelimella.

Tietokantayhteys

Sovelluksen alussa pitää luoda tietokantaan yhteys, jota käytetään koko suorituksen ajan. Yhteyden avaaminen on aina raskas prosessi, joten turhaan yhteyttä ei pidä aukoa ja sulkea. Suurissa sovelluksissa tietokantayhteys pidetään auki pitempään ja samaa yhteyttä käytetään uudelleen ja uudelleen.

Kts. Database Connections in Python: Extensible, Reusable, and Secure

Kyselyt

Tietokantaan kohdistuvat kyselyt ovat jokaisen sovelluksen perusta. Jos SQL-kyselyiden kirjoittaminen tuottaa suuria hankaluuksia voit harjoitella niitä Henkilökohtaisen tiedonhallinnan perusteet -kurssin demotehtävillä.

  1. Valmistele yksinkertainen kysely:
    sql = """
    SELECT nimi as Nimi, kuvaus as Kuvaus, henkilomaara as Henkilomaara
    FROM resepti
    """
  2. Luo Cursor-objekti ja suorita (execute) kysely. Mahdolliset virhetilanteet kannattaa napata kiinni try..except-lohkolla.
    cur = con.cursor()
    cur.execute(sql)
    
  3. Luo tuloksena saaduista tietueista Jinja-templatelle sopiva tietorakenne. Seuraavassa on esimerkki
    sql = """
    SELECT nimi as Nimi, kuvaus as Kuvaus, henkilomaara as Henkilomaara
    FROM resepti
    """
    cur = con.cursor()
    
    cur.execute(sql) # suoritetaan kysely
    reseptit = cur.fetchall() #haetaan kaikki kyselyn tulokset
    return render_template('reseptit.html', reseptit=reseptit)
    
  4. Vie reseptit templatelle. Voit ensimmäisessä versiossa suoraan tulostaa tietorakenteen templatessa näkyville.
    {{ reseptit }}
    Kokeile suorittaa ohjelmasi selaimessa. Tee tämän jälkeen siistimpi tulostusversio eli käy näkymässä silmukassa läpi reseptit-rakenne, joka toimii kuten dict, ja tulosta sen sisältö kauniisti taulukkona. Esim.
    <table>
    <caption>Reseptit</caption>
    {%for r in reseptit:%}
    <tr><th>{{r['nimi']}}</th>
    <td>{{r['kuvaus']}}</td>
    <td>{{r['henkilomaara']}}</td>
    {%endfor%}
    </table>

    Kenttien nimissä isoilla ja pienillä kirjaimilla ei ole merkitystä.

  5. Yleensä kyselyjä pitää rajoittaa sopivilla ehdoilla, jotka muuttuvat ohjelman suorituksen mukana. Tällöin kyselystä tulee dynaaminen. Muuta edellistä kyselyä seuraavanlaiseksi:
    # valmistellaan kysely ja sijoitetaan dynaamisen arvon tilalle :lkm-muuttuja
    sql = '''SELECT Nimi, Kuvaus, Henkilomaara, ReseptiID, RuokaLajiID
        FROM Resepti 
        WHERE henkilomaara = :lukumaara
        '''
        lkm = 4
        cur.execute( sql, {"lukumaara":lkm})
    

    Kokeile sivun toimintaa Kokeile muuttaa lkm-muuttujan arvoa.

    lukumaara-placeholderin nimessä on merkitystä isoilla ja pienillä kirjaimilla. :lukumaara on eri asia kuin :Lukumaara.
  6. Yritä käydä sama kursori läpi silmukalla kahteen kertaan. Jälkimmäisellä kerralla ei pitäisi löytyä mitään. Kursori antaa kertaalleen tietokantakyselyn tulokset ja jos haluaa ne uudelleen on suoritettava execute uudelleen. Samaa kyselyä ei kannata suorittaa useaan kertaan vaan kannattaa ottaa heti talteen mitä tarvitsee.
  7. Hae tietokannasta kaikki reseptit ja vie ne templatelle. Hae myös kaikki ruokalajit. Tulosta nämä kaikki siististi näkyville www-sivullesi niin voit helpommin testata seuraavia tehtäviä, kun sivua uudelleen ladattaessa näet heti mitä muutoksia sisältöön on tullut.

Muuttujia saa sijoittaa kyselyihin vain edellämainitulla tavalla tai käyttäen ?-merkki placeholdereita! Missään tapauksessa sijoittamista ei saa tehdä normaaleilla merkkijono-operaatioilla. Oikein toimiessa tietokanta pitää itse huolen siitä, että sijoitettavat arvot ovat tarpeen mukaan heittomerkkien sisällä tai ilman. Tietokanta pitää myös huolen, että kenttiin ei tule vääräntyyppistä tietoa. Kyselyjen valmistelu parantaa tietoturvaa SQL Injection -tyyppisiä hyökkäyksiä vastaan.

Lisääminen

Tietojen lisääminen tapahtuu valmisteltujen kyselyjen avulla aivan samaan tapaan kuin kyselyjenkin tekeminen:

  1. Lisätään tietokantaan uusi resepti. Luo suusi valmisteltava kysely muotoon:
    INSERT INTO resepti (Nimi, Kuvaus, Henkilomaara, RuokalajiID, ReseptiID)
    VALUES (:nimi, :kuvaus, :henkilomaara, :ruokalaji, :reseptiid)
  2. Kehittele jokin reseptin nimi ja lyhyt kuvaus (esim. makaronilaatikko) ja luo muuttujat kaikille parametreille. resepti.sql:stä voit katsoa millaisilla ID-arvoilla ruokalajeja on olemassa. Anna ReseptiID:ksi joku sellainen numero mitä ei vielä ole tietokannassa (esim. 10).
  3. Sido muuttujat kyselyyn ja suorita sovelluksesi lataamalla sivu uudelleen.
  4. Kokeile suorittaa saman reseptin lisääminen uudelleen. Onnistuuko vai saatko virheilmoituksen? Ota try..exceptillä kiinni mahdolliset virheet. Muistathan, että lisäys ei jää voimaan, ellei sinulla ole autocommit-moodi (isolation_level=none) päällä jolloin transaktio täytyy hyväksyä:
    con.commit()
    Muistathan myös, että kahta reseptiä ei voi lisätä samalla reseptiID:llä (perusavaimen eheys!). Kokeile millaisen virheilmoituksen saat. Entäs jos yrität käyttää ruokalajin id:nä väärää arvoa? Kokeile millainen virheilmoitus nyt tulee.
    • IntegrityError: PRIMARY KEY must be unique
    • IntegrityError: Constraint failed: Ruokalajia ei löydy
  5. Kokeile jättää reseptiid kokonaan pois INSERT-lauseesta:
    INSERT INTO resepti (Nimi, Kuvaus, Henkilomaara, RuokalajiID)
    VALUES (:nimi, :kuvaus, :henkilomaara, :ruokalaji)
    Poista reseptiid myös execute-kutsun parametreista. Miksi tämä versio toimii?

    Tietokannan rakenteessa on määritelty, että reseptiid on tyypiltään autoincrement, jolloin tietokanta keksii sen tarvittaessa itse. Tämä ei ole standardia SQL-kieltä vaan sqlite3:n ominaisuus. Esim. Postgresql-tietokannassa sama onnistuisi käyttämällä tyyppiä SERIAL.

    ReseptiID INTEGER PRIMARY KEY AUTOINCREMENT

Poistaminen

  1. Lisää uusi kysely. Poistetaan edellä lisätty resepti.
    DELETE FROM resepti WHERE reseptiID = :id
  2. Luo id-muuttuja ja sille arvo, joka löytyy tietokannasta. Esim. numero 2. (kts. resepti.sql). Sido muuttuja kyselyyn. Kokeile onnistuuko poistaminen.
  3. Yritä poistaa ruokalajeja. Onnistuuko se ilman ongelmia? Voitko vapaasti poistaa minkä tahansa ruokalajin?
    IntegrityError: constraint failed: Ruokalajia ei voida poistaa, koska se on käytössä

    Käytössä olevia ruokalajea ei voi poistaa viite-eheyden takia:

    CONSTRAINT Resepti_RuokalajiID
            FOREIGN KEY (RuokalajiID)
            REFERENCES Ruokalaji (RuokalajiID)
    
  4. Kokeile poistaa kerralla useampia tietueita. Tämä onnistuu esim. kyselyllä:
    DELETE FROM taulu WHERE tunniste IN (1, 2, 3)
  5. Pohdi miten tekisit poistamisen hakusanan perusteella (ei tarvitse tässä toteuttaa).
  6. Ole hyvin tarkkana tehdessäsi poistoja ja päivityksiä. Jos haluat poistaa tai päivittää juuri tietyn tietueen, sinun pitää aina viitata siihen käyttäen perusavainta, joka yksilöi tietueen. Jos käytät muita ehtoja, helposti poistat tai päivität vääriä tietueita.

Päivittäminen

  1. Päivittäminen tapahtuu tismalleen samaan tapaan kuin poistaminenkin, mutta nyt pitää antaa myös päivitettävien kenttien uusi sisältö.
  2. Tee uusi kysely
    UPDATE resepti SET henkilomaara = :maara WHERE reseptiID = :reseptiID
  3. Muuta jonkun lisäämäsi tietueen henkilömäärä toiseksi luvuksi.
  4. Aja kysely napauttamalla selaimen Refresh-painiketta.
  5. Tarkista, että muutos tapahtui tietokannan komentorivikäyttöliittymästä antamalla SQL-kysely:
    SELECT * FROM resepti WHERE reseptiid = ?;

Monimutkaisemmat tulostukset

Kyselyjen kohdistuessa useampaan tauluun voi tulla kiusaus kirjoittaa silmukka, joka tekee useita hyvin samankaltaisia kyselyjä tietokantaan. Tietokanta menee kuitenkin helposti jumiin, jos siihen kohdistuu kyselyjä hirvittävän nopealla tahdilla. Hyvänä perussääntönä voi pitää: minimoi tietokantaan kohdistuvien erillisten operaatioiden määrä. Yksittäisessä kyselyssä kannattaa kuitenkin tietokanta laittaa tekemään kaikki mahdolliset laskutoimitukset ja järjestämiset.

  1. Listataan kaikki ruokalajit otsikkoina ja otsikon alle ruokalajiin liittyvät reseptit välilyönneillä eroteltuna. Haetaan ensin kaikki ruokalajit ja niihin liittyvät reseptit.
    SELECT ruokalaji.nimi AS ruokalaji, resepti.nimi AS resepti 
    FROM resepti, ruokalaji
    WHERE ruokalaji.ruokalajiID = resepti.ruokalajiID
    ORDER BY ruokalaji.nimi, resepti.nimi
  2. Aja kysely ja käy for-silmukalla se läpi ja tulosta aluksi kultakin riviltä löytyvä ruokalaji h2-elementin sisälle.
  3. Nyt ruokalaji toistuu turhan moneen kertaan. Tutki onko ruokalaji sama kuin edellisellä silmukan kierroksella ja tee tulostus vain ensimmäisellä kerralla. Tässä kannattaa käyttää apuna Jinjan loop-muuttujia. Esim. loop.previtem.
  4. Nyt halutaan tulostaa vielä reseptit ruokalajin alle. Tulosta reseptien nimet aina niihin liittyvän ruokalajin nimen alle esim. p-elementin sisään.
  5. Tarkista, että h2 ja p-elementit tulostuvat järkevästi niin, että koodi pystyy validina.
  6. Mitäs jos haluaisit listata reseptit listana? (ul ja li-elementeillä). Kokeile muuttaa listaksi. Varmista, että rakenne pysyy validina.
  7. Entäs jos halutaankin listata kaikki ruokalajit? Nythän listattiin vain sellaiset, joille on määritelty reseptejä. Kyselyä pitää muuttaa seuraavasti:
    SELECT ruokalaji.nimi AS ruokalaji, resepti.nimi AS resepti
    FROM ruokalaji LEFT OUTER JOIN resepti
    ON ruokalaji.ruokalajiID = resepti.ruokalajiID
    ORDER BY ruokalaji.nimi, resepti.nimi
  8. Lisäksi on tarkistettava onko rivillä ruokalaji ja resepti vai pelkkä ruokalaji ja tehtävä tulostus sen mukaisesti.
  9. Kokeile miten uusittu tulostus toimii.

Transaktiot

Transaktioiden avulla pystytään hallitsemaan tietokantaoperaatiokokonaisuuksia. Esim. tietokantaan pitää syöttää useita tietueita peräkkäin ja jos yksikin lisäys epäonnistuu, niin kaikki lisäykset pitää pystyä peruuttamaan. Transaktio varmistaa, että keskeneräisen transaktion vaikutukset tietokantaan eivät heijastu muille tietokannan käyttäjille, kuin vasta transaktion hyväksymisen jälkeen.

  1. Harjoitellaan transaktioiden toimintaa lisäämällä samalla kerralla uusi ruokalaji ja siihen liittyviä reseptejä. Valmistele kaksi kyselyä, joista toinen lisää ruokalajin ja toinen reseptin.
  2. Transaktio täytyy aloittaa BEGIN-komennolla, jos autocommit-moodi (isolation_level=None) on päällä:
    cur.execute( "BEGIN" )

    sqlite-kirjastossa on oletuksena transaktiot käytössä koko ajan eli jokainen tietokannan sisältöön kohdistuva muutos täytyy erikseen kommitoida. autocommit-moodi (isolation_level=None) taas kommitoi automaattisesti kaiken. Web-sovelluksessa kannattaa useimmiten pitää autocommit-moodi päällä ja vain erikseen tarvittaessa aloittaa transaktio BEGIN-komennolla.

  3. Tee lisäys kuten edellä eli luo muuttuja ja sido ne kyselyihin. Aseta reseptin ruokalajiID:ksi sama kuin mikä keksimälläsi ruokalajilla on. Laita reseptin reseptiID:ksi jokin sellainen numero mikä on jo olemassa tietokannassa eli reseptin lisääminen epäonnistuu.
  4. Kokeile suorittaa kysely. Muista tehdä tarkistukset kyselyiden onnistumiselle (try...except). Nyt pitäisi tulla virheilmoitus reseptin lisäyksen epäonnistumisesta. Virheen tapahtuessa tee rollback eli peruuta jo tehdyt muutokset:
    con.rollback()
    Tarkista tietokannan komentorivikäyttöliittymästä onnistuiko ruokalajin lisäys.
  5. Lisää laskuri virheiden määrälle, joka on aluksi nolla.
  6. Jos SQL-lauseiden suorituksessa tulee virhe, niin lisää virhelaskurin määrää.
  7. Kaikkien SQL-lauseiden lopussa tee tarkistus onko tullut virheitä. Jos virheitä ei ole tullut, niin suorita con.commit() ja muussa tapauksessa con.rollback().

Automaattiset avainkentät

Usein käytetään avaimina kenttiä, joiden arvon keksiminen jätetään tietokannan harteille. Esim. reseptiID ja ruokalajiID ovat tällaisia kenttiä. Tähän saakka ne on keksitty itse, mutta entäs jos tietokanta keksii ne?

  1. Muutetaan edellistä lisäystä hieman. Poista kyselyistä reseptin INSERT-kyselystä reseptiID ja ruokalajin lisäyksestä ruokalajiID.
  2. Mistä nyt tiedämme mikä on reseptin ruokalajin id-numero? Tämän saa selville pyytämällä cursorilta:
    ruokalajiID = cur.lastrowid

    saman voi myös kysyä kyselynä:

    SELECT last_insert_rowid()
  3. Kokeile ohjelmaa. Saat luultavasti virheen, miksi? Korjaa ruokalajin tietoja niin, että lisäys onnistuu.

Kirjautuminen ja tietokanta

Yleensä kirjautumisessa salasana ja tunnus tarkistetaan tietokannasta. Reseptitietokannassa on mukana user-taulu, jossa on kolme eri tunnusta ja salasanaa:

TunnusSalasana
foobar
barfoo
opiskelijaties4080
Salasanojen tarkisteet on tehty seuraavasti:
salt = "suolaasuolaa"
m = hashlib.sha512()
m.update(salt.encode("UTF-8"))
m.update(salasana.encode("UTF-8"))
m.hexdigest() # tämä on tietokantaan tallennettu salasanan tarkiste
Muokkaa aiemmin tekemääsi kirjautumista siten, että edellytät kirjautumista reseptitietokannan sivuille ja käytät tietokantaan tallennettuja tunnuksia.

Malliratkaisu, lähdekoodi ja template

Lisätietoa

Kannattaa ehdottomasti tutustua Philip Greenspunin kirjaan: SQL for Web Nerds

PostgreSQL

Voit käsitellä tietokantaasi aivan samalla tavalla kuin sqlite3-tietokantaa. Joudut vain avaamaan tietokantayhteyden eri kirjaston avulla

PostgreSQL vs SQLite3

SQL-syntaksin eroavaisuuksia SQLite3- ja PostgreSQL-tietokantojen välillä.

SQLite3PostgreSQL
AUTOINCREMENTSERIAL, jolla korvataan INTEGER-tietotyyppi:
CREATE TABLE taulu(
      id SERIAL PRIMARY KEY,
      nimi VARCHAR(256) NOT NULL
);
edellämainittu on sama kuin seuraava:
CREATE SEQUENCE taulu_id_seq;

CREATE TABLE taulu (
    id integer NOT NULL DEFAULT nextval('taulu_id_seq')
    nimi VARCHAR(256) NOT NULL
    );

    ALTER SEQUENCE taulu_id_seq
    OWNED BY taulu.id;
Viimeksi käytetyn arvon saa kyselyllä:
SELECT currval(pg_get_serial_sequence('taulu', 'id'));
tai saman saa myös INSERT-lauseen yhteydessä seuraavasti:
INSERT INTO taulu(nimi) 
VALUES('Foobar')
RETURNING id;
DATETIMETIMESTAMP

PythonAnywhere ja MySQL

PythonAnywhere-palvelussa voit käyttää suosittua MySQL-tietokantaa. MySQL on joissain tapauksissa ominaisuuksiltaan vajavainen relaatiotietokanta. Jos mahdollista, käytä ennemmin PostgreSQL-tietokantaa, johon palataan myöhemmin tällä kurssilla. Tarvittaessa yös SQLite-tietokanta toimii PythonAnywhere-palvelussa.

Käyttäjien kommentit

Kommentoi Lisää kommentti
Informaatioteknologia - Jyväskylän yliopiston informaatioteknologian tiedekunta