accès variables de connexion[réglé]

2

Réponses

  • toolsDevtoolsDev Membre
    janvier 2017 modifié #32

    ça je l'avais déjà  fait 



    var resultSet: Row?

    Comprends moi bien, étant donné que cela ne fonctionnait toujours pas, j'ai essayé différentes choses


     


    D'ou le code actuel.


     


    Je me doute que dans la structure/classe peu importe, il faut utiliser la référence à  CETTE instance, mais cela ne donnait rien donc, oui j'ai utilisé autre chose...


     


    Tu sais comme dans un pointer-cliquer ou tu vois très bien ce qu'il faut faire, mais que rien de logique ne fonctionne et à  un moment donné (désespoir) tu fais n'importe quoi...


     


    Ayant la librairie d'installer, je n'ai AUCUNE autre erreur de compilation, juste celle du type pour ma variable, je ne vois pas d'ou viendrais ton souci, à  part le fait que toi tu n'as pas installé les outils.


     


    voilà , voilà ...


     


    Edit:


    pour le mutable sur ma méthode, moi aussi le compilateur me l'avait proposé, mais en vain.


  • Joanna CarterJoanna Carter Membre, Modérateur


    ça je l'avais déjà  fait 



    var resultSet: Row?



     


     


    Bah, non, tu n'as pas fait ce que j'ai proposé. Il faut écrire la var comme je l'ai écrit et la var private.


     




    Je me doute que dans la structure/classe peu importe, il faut utiliser la référence à  CETTE instance, mais cela ne donnait rien donc, oui j'ai utilisé autre chose...




     


    Quoi ? Tous que tu écris à  importance sur la réussite de ton code.


     


    Tu comprends le concept de self ? Si oui, dis moi ce tu en entends et pourquoi tu as essayé d'utiliser sharedInstance dans tes méthodes ?

  • toolsDevtoolsDev Membre
    janvier 2017 modifié #34

    self. fait référence à  l'intance présente, donc à  l'objet en cours.


    Et pourquoi j'ai essayé de passer par le singleton ? comme déjà , car rien ne fonctionnait et je désespérais, donc j'ai fait des bêtises, pour tester le comportement...


     


    Concernant ta proposition, je ne vois pas comment l'utiliser et ou la placer :



    var resultSet: Row?
    {
    get
    {
    return _resultSet
    }
    mutating set
    {
    _resultSet = newValue // je ne comprends pas d'ou sort newValue ?
    }

    pour l'affectation ça je sais :




    // self. ce réfère donc à  la propriété de cette instance (donc du singleton) :
    self.resultSet = tables_data

    //puis pour l'appeler ailleurs dans mon projet, j(utilise l'instance de ConnexionSQLite (le singleton):
    ConnexionSQLite.sharedInstance.resultSet

    Mon raisonnement (ma compréhension) est il complètement à  la ramasse, ou c'est correct ?


     


    Edite :


     


    Pour qu'il n'y est de mal entendu, voici mon code :



    import Foundation
    import GRDB

    struct ConnexionSQLite
    {
    static var sharedInstance = ConnexionSQLite()
    private var _resultSet: Row?

    var resultSet: Row?
    {
    get
    {
    return _resultSet
    }
    mutating set
    {
    _resultSet = newValue
    }
    }


    lazy var dbQueue: DatabaseQueue? =
    {
    do
    {
    let dbQueue = try DatabaseQueue(path: "games.sqlite3")
    return dbQueue
    }
    catch
    {
    print(error)
    return nil
    }
    }()

    private init(){/* je devrais appeler getallData() une fois l'instance créée la méthode charge resultSet et à  mon appelle de resulSet (ailleurs) le contenu est dispo ?! */}

    mutating func getallData()
    {
    guard let dbQueue = ConnexionSQLite.sharedInstance.dbQueue else
    {
    return
    }

    do
    {
    let tables = try dbQueue.inDatabase
    {
    db in
    try Row.fetchAll(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")
    }

    for row in tables
    {
    let table: String = row.value(named: "name")
    let tables_data = try dbQueue.inDatabase
    {
    db in
    try Row.fetchAll(db, "SELECT * FROM \(table)")
    }
    self.resultSet = tables_data
    }
    }
    catch
    {
    print(error)
    }
    }
    }



    ViewController pour l'appelle de ma méthode :



    let data = ConnexionSQLite.sharedInstance.resultSet
    print(data)


    et avec ceci j'ai toujours la même erreur sur la ligne self.resultSet = tables_data


    à  savoir : Cannot assign value of type '[Row]' to type 'Row?' 


  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #35

    guard let dbQueue = ConnexionSQLite.sharedInstance.dbQueue else
    {
    return
    }

    Tu n'as pas corrigé cette référence à  sharedInstance pour self



    let tables_data = try dbQueue.inDatabase
    {
    db in

    try Row.fetchAll(db, "SELECT * FROM \(table)")
    }

    self.resultSet = tables_data

    De ce que j'ai appris de GRDB, Row.fetchAll(...) renvoie un Array de Row [Row]. Du coup, resultSet devrait être de ce type, pas Row?


     


     


     


    À part de ces fautes, tu peut nous dire exactement (sans code) ce que attends faire avec ce projet ?


     


    Est-ce que tu veux accéder plusieurs BDD SQLite ?


     


    Après avoir accéder le structure des tables, tu veux accéder les données là  dedans ?


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #36

    je ne comprends pas du tout ce que tu me dis, surtout concernant le ".self"


     


    ce que tu as quoté est différent de ce que tu m'avais déjà  proposé, si je fais un print() de tables_data, tout est ok ! donc mon erreur est bien au niveau de ma compréhension de la variable globale.


     


    Et avant de continuer, j'aimerais déjà  comprendre ceci : (et pas avancer bêtement)



    private var _resultSet:[Row] // j'ai modifié le type.

    var resultSet:[Row]
    {
    get
    {
    return _resultSet
    }
    mutating set
    {
    _resultSet = newValue
    }

    par ce que là , cela me paume complètement..


     


    tu voudrais que je fasse ça ? :



    guard let dbQueue = ConnexionSQLite.self.sharedInstance.dbQueue else
    {
    return
    }

    Non... Je ne dois pas comprendre ce que tu me proposes, c'est pas possible autrement...


    En plus, mon code fonctionne parfaitement sans erreur, jusqu'à  l'affectation de la variable via table_datas.


     


    ce que je veux ? (juste avoir les données de getAllData disponibles, soit en variable globale, soit en return)


     


    Une fois que mon singleton est instancié, je veux déclencher la méthode getAllData, puis remplir une variable des datas récupérées par la requête, puis finalement pouvoir y accéder ailleurs, pour par exemple remplir une tableView.


     


    Je ne vois pas pourquoi cela devient si complexe...


     


    Donc, niveau conception, voilà  ce que je vois dans les grandes lignes :


     


    L'inti() (de ConnexionSQLite) contenant l'appel de la méthode {self.getAllData} et la méthode getAllData qui return une variable avec ... mes datas, ou une variable globale disponible.


     


    Voilà , j'espère avoir été plus clair et je te remercie encore pour ton aide.


     


    Edit :


     


    ha oui, autre chose que vous avez surement déjà  tous compris, mais je le re-précise :


     


    Je souhaite au lancement de mon application, charger une variable de toutes les données de mes tables(via getAllData).


    Pour ne plus refaire de requêtes et donc ne plus user de ressources.


    Une itération au besoin, se fera depuis cette même variable pour en extraire son contenu et l'afficher dans une tableView ou autre. 


  • Joanna CarterJoanna Carter Membre, Modérateur


    je ne comprends pas du tout ce que tu me dis, surtout concernant le ".self"


    tu voudrais que je fasse ça :



    guard let dbQueue = ConnexionSQLite.self.sharedInstance.dbQueue else
    {
    return
    }

    non je dois pas comprendre, c'est pas possible...




     


    Non, c'est tout simplement :



    guard let dbQueue = self.dbQueue else
    {
    return
    }

    Tu comprends ce que ça fasse ? Dis-moi.


     


     




    ce que je veux ? (juste avoir les données de getAllData disponible)


     


    Une fois que mon singleton est instancié, déclenché la méthode getAllData, puis remplir une variable des data récupérées par la requête, puis pouvoir y accéder ailleurs, pour par exemple remplir une tableView.




     


     


    Bon. Après que tu as entendu comment coder la struct et ses méthodes correctement, avec les bons types


     




    Je ne vois pas pourquoi cela est si complexe, mais pas du tout.




     


     


    1. Il faut lire la documentation d'Apple sur le coding


     


    2. Il faut lire la documentation pour GRDB, lire son code et comprendre ce qui se passe là . Sinon, tu ne pigeras jamais comment récupérer les données que tu veux.


     


    Moi, je suis consultante, analyste, programmeuse et enseignante en analyse et programmation pendant 25 ans et, toujours, il met du temps pour apprendre les nouvelles compétences.


     


    Mais il faut commencer avec les petits pas. On ne peut pas présumer que ce que l'on a appris dans un autre environnement convient dans un nouveau.


     


    Du coup, je propose que tu recommences à  zéro.


     


    Tout d'abord, supprimes tous ton code.


     


    Nous nous occuperons de parler avec un framework SQLite, qui contient plusieurs méthodes qui "throw" ; du coup, le code sera plus complexe que normal.


     


    Allons-y...



    struct ConnexionSQLite
    {
    enum ConnexionError : Error
    {
    case dbQueueError
    }

    static var sharedInstance: ConnexionSQLite? = ConnexionSQLite()

    D'abord, nous aurons besoin de nos propres Errors, on peut les étendre plus tard.



    private lazy var dbQueue: DatabaseQueue? =
    {
    do
    {
    let dbQueue = try DatabaseQueue(path: "games.sqlite3")

    return dbQueue
    }
    catch
    {
    return nil
    }
    }()

    Tout dépendra si le fichier sqlite existe ou non. Si on ne le trouve pas, dbQueue renvoie nil.


     


    Comment peut-on déterminer si la connection soit valide ou no ?


     


    Dans le code de ViewController, ou autre, nous pourrions utiliser quelque chose comme :



    guard let connexion = ConnexionSQLite.sharedInstance else
    {
    return
    }

    C'est à  dire, si le fichier n'existe pas, le init() de ConnexionSQLite renverra nil, du coup, sharedInstance sera nil. Pour réaliser cette fonctionnalité, il faut créer un "failable initialiser" :



    private init?()
    {
    do
    {
    try self.fetchTableMetadata()
    }
    catch
    {
    return nil
    }
    }

    Le code dans fetchTableMetadata sera le premier d'appeler notre lazy var dbQueue et, pour le tester, on fait :



    mutating func fetchTableMetadata() throws
    {
    throw ConnexionError.dbQueueError // seulement pour tester

    }

    Maintenant, tous le code de dessus ensemble :



    struct ConnexionSQLite
    {
    enum ConnexionError : Error
    {
    case dbQueueError
    }

    static var sharedInstance: ConnexionSQLite? = ConnexionSQLite()

    private lazy var dbQueue: DatabaseQueue? =
    {
    do
    {
    let dbQueue = try DatabaseQueue(path: "fauxChemin.sqlite3") // pour tester

    return dbQueue
    }
    catch
    {
    return nil
    }
    }()

    private init?()
    {
    do
    {
    try self.fetchTableMetadata()
    }
    catch
    {
    return nil
    }
    }

    mutating func fetchTableMetadata() rethrows
    {
    guard let dbQueue = self.dbQueue else
    {
    throw ConnexionError.dbQueueError
    }

    // ...
    }
    }

    Et le code dans une ViewController :



    override func viewDidLoad()
    {
    super.viewDidLoad()

    guard let connexion = ConnexionSQLite.sharedInstance else
    {
    print("nil connexion")

    return
    }

    print("bonne connexion")
    }
  • toolsDevtoolsDev Membre
    janvier 2017 modifié #38


     


    Non, c'est tout simplement :


    guard let dbQueue = self.dbQueue else

    {

    return

    }

    Tu comprends ce que ça fasse ? Dis-moi.




    là , je comprends bien mieux ! ( et pour tout dire j'osais pas trop vous en parler, mais je trouvais ça étrange quand j'ai quoté mon code...)


    L'instance état déjà  créée, je n'ai donc pas besoin de faire ça :



    ConnexionSQLite.sharedInstance.dbQueue
     

    Par contre, comprends juste, que dans le feu de l'action et les copier/coller multiples j'ai énormément perdu confiance en moi et je n'ai pas vérifié les choses évidentes...


     


    Bref, ceci étant, je comprends mieux ce que tu me disais pour ce morceau de code.


    le .self réfère la variable du singleton (de l'instance)


    exact ?


     


     




    Du coup, je propose que tu recommences à  zéro.


     


    Tout d'abord, supprimes tous ton code.


     


    Nous nous occuperons de parler avec un framework SQLite, qui contient plusieurs méthodes qui "throw" ; du coup, le code sera plus complexe que normal.


     


    Allons-y...



     


     


     


    Par contre tu veux que je recommence tout mon projet avec le framework https://github.com/stephencelis/SQLite.swift ?


     


    Je me suis habitué à  GRDB.swift et à  sa syntaxe, si je change tout le temps, comprends que je serais encore plus perdu.


    D'un point de vue pédagogique je ne pense pas que cela me soit très adapté.


     


    Après si ce Framework est meilleur pour ma compréhension, ok. Mais il faut que cela me soit bénéfique.


     


    Sur vos conseils à  tous (surtout ceux de Céroce) j'avais décidé de choisir GRDB.swift, pourquoi encore changer ?


     


    Sinon, je lis avec attention tes réponses et analyse tout ça, en ce moment même.


     


    Edit :


    Attention, ce n'est pas par fainéantise que je t'ai fait remarquer ma crainte de tout recommencer via un nouveau Framework !


    j'utilise cocoapods, cela est très simple et rapide à  initier...


    Mais bien, pour en tiré bénéfice.


     


    Edit 2:


     


    Et avant de courir 2 lièvres en même temps, j'aimerais bien comprendre mon erreur, concernant la variable globale de mon code ?


    À savoir récupérer toutes mes métas données de ma méthode getALLData dans (par exemple) la vue.



    // exemple dans ma vue de ce que je souhaite :
    let data = ConnexionSQLite.sharedInstance.resultSet

    ...itération de data .... 

    et bien séparer toutes mes classes/structures (MVC) comme je l'ai fait jusqu'à  présent. Pour un meilleur débogage, je trouve ça plus sain, surtout quand on débute.


     


    Edit 3 :


    j'ai réglé mon souci de variable globale et j'avoue m'être bien pris la tête à  chercher tout et n'importe quoi, alors que c'était très simple  :




    // un tableau vide ressemble donc à  ça :
    var resultSet:[Row]=[]

    // et non à  ça :
    var resultSet:[Row]=[:]


  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #39


    Bref, ceci étant, je comprends mieux ce que tu me disais pour ce morceau de code.


    le .self réfère la variable du singleton (de l'instance)


    exact ?




     


    Oui et non  ;)


     


    La variable du singleton n'est qu'un membre statique de la struct/classe, qui tient un instance de la struct/classe et qui (dès son instantiation) vit pour la durée de vie de l'application.


     


    On ne peut pas accéder self sauf si on est dans une des méthodes d'instance de la struct/classe. On peut dire que self est une référence interne, alors que tous les autres références sont là  pour l'utilisation externe.


     


    Si on créait plusieurs instances d'une struct/classe (non-singleton), self ferait référence à  l'instance que l'on est en cours d'utiliser.


     


    Une struct/classe se comporte comme un emporte-pièce ; tous que l'on trouve là  dedans fait partie de l'emporte-pièce.


     


    Les instance des structs/classes se comportent comme les petit gâteaux qui sont formées par l'emporte-pièce.


     


    Un singleton est un emporte-pièce qui ne peut que créer un seul petit gâteau.


     




    Par contre tu veux que je recommence tout mon projet avec le framework https://github.com/stephencelis/SQLite.swift ?


     


    Je me suis habitué à  GRDB.swift et à  sa syntaxe, si je change tout le temps, comprends que je serais encore plus perdu.


    D'un point de vue pédagogique je ne pense pas que cela me soit très adapté.


     


    Après si ce Framework est meilleur pour ma compréhension, ok. Mais il faut que cela me soit bénéfique.


     


    Sur vos conseils à  tous (surtout ceux de Céroce) j'avais décidé de choisir GRDB.swift, pourquoi encore changer ?




     


     


    Moi, je n'ai pas de préférence ; j'utilise CoreData qui fait l'arbitre entre le monde des objets et SQLite lui-même. Avec ça on ne travaille qu'avec les objets et CoreData s'occupe de leurs stockage et leurs récupération.


     


     




    Et avant de courir 2 lièvres en même temps, j'aimerais bien comprendre mon erreur, concernant la variable globale de mon code ?


    À savoir récupérer toutes mes métas données de ma méthode getALLData dans (par exemple) la vue.




     


     


    S'il ne te dérange trop, il y a plus que je voudrais te montrer dans la méthode fetchTableMetadata mais il est tard.


     


    Il suffit de dire, avant de m'endormir, que les noms des vars et méthodes devrait avoir une importance relatifs à  leurs fonctionnes ; getAllData n'est pas le plus adapté pour décrire ce que tu fasse ; dans ce struct la, tu t'occupes plutôt des métadonnées d'un BDD, du coup, fetchTableMetadata  :)


     


    À demain  -_-


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #40

    Bonjour Joanna Carter,


    Désolé pour hier soir je n'ai pas put venir.


     


    Je viens de créer un projet avec SQLite.swift, la librairie est prête.


     


    Tout est refait comme tu me l'as proposé, je vais maintenant analyser ton code, en y ajoutant des commentaires pour que tu puisses voir si je comprends bien tout avant d'avancer plus loin.


     


    Conscient que tu prennes beaucoup de ton temps libre pour m'expliquer les concepts évoqué plus haut, sache que je te remercie pour le temps et la pédagogie que tu me donnes.   


     


    Edit


    Voilà  mon code, que j'ai un peut modifié car j'avais des erreurs :



    import Foundation
    import SQLite

    struct ConnexionSQLite
    {
    // Création d'une liste de type Error, pour personnaliser et afficher les erreurs de connexion:
    enum ConnexionError : Error
    {
    case dbQueueError
    }

    // Création du Singleton:
    static var sharedInstance: ConnexionSQLite? = ConnexionSQLite()


    // Création, test et affectation de la variable de connexion à  la base de données:
    // j'ai modifié le type DatabaseQueue pour Connection (je pense que tu as du te tromper, cette classe "Connection" n'existe pas.

    private lazy var dbQueue: Connection? =
    {
    do
    {
    // Si le test de la connexion est réussis on renvoi dbQueue:
    let dbQueue = try Connection("games.sqlite3")

    return dbQueue
    }
    catch
    {
    return nil
    }
    }()

    // A l'instanciation du Singleton fetchTableMetadata est appelé:
    private init?()
    {
    do
    {
    try self.fetchTableMetadata()
    }
    catch
    {
    return nil
    }
    }

    // On affiche "juste" si la connexion est ok ou pas:
    // j'ai plusieurs erreurs sur cette méthode et je ne comprends pas le "rethrows" ? :
    'rethrows' function must take a throwing function argument

    mutating func fetchTableMetadata() rethrows
    {
    guard let dbQueue = self.dbQueue else
    {
    throw ConnexionError.dbQueueError
    }

    // ...
    }
    }


    ViewController:



    // là  encore j'ai une erreur et je ne comprends pas, dans le sens ou connexion n'est jamais utilisé :
    guard let connexion = ConnexionSQLite.sharedInstance else
    {
    print("nil connexion")

    return
    }

    print("bonne connexion")
  • Joanna CarterJoanna Carter Membre, Modérateur

    Dans le développement d'un logiciel, tu profiterais plus de le faire pas après pas.


     


    D'abord, nous avons commencé à  construire la struct singleton qui emballera toute la fonctionnalité SQLite.


     


    Avant d'écrire le code qui parlera avec le framework, il faut vérifier si l'emballage marche comme prévu. on peut renvoyé les données de test avant de toucher la vrai fonctionnalité de SQLite.swift.


     


    Est-ce que tu as testé que, si on mettait le mauvais chemin vers un fichier, que le code réagisse correctement, en renvoyant nil pour le singleton ? Et pour un bon chemin, qu'il renvoie une référence valide ?


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #42

    alors comme je te disais, là , j'ai des erreurs à  la compilation, j'en ai trouvé une ou deux (de petites erreurs de nom d'objet du Framework)


    mais si tu vois mes annotations (dans le code) tu verras que la méthode fetchTableMetadata me pose soucis, ainsi que son appel dans le ViewController.


  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #43

    Désolée ; erreur de copier/coller.



    // On affiche "juste" si la connexion est ok ou pas:
    // j'ai plusieurs erreurs sur cette méthode et je ne comprends pas le "rethrows" ? :
    'rethrows' function must take a throwing function argument

    mutating func fetchTableMetadata() rethrows
    {
    guard let dbQueue = self.dbQueue else
    {
    throw ConnexionError.dbQueueError
    }

    // ...
    }

    ... devrait être :



    mutating func fetchTableMetadata() throws
    {
    guard let dbQueue = self.dbQueue else
    {
    throw ConnexionError.dbQueueError
    }

    // ...
    }

    Et :



    // là  encore j'ai une erreur et je ne comprends pas, dans le sens ou connexion n'est jamais utilisé :
    guard let connexion = ConnexionSQLite.sharedInstance else
    {
    print("nil connexion")

    return
    }

    print("bonne connexion")

    Là , ce n'est pas une erreur ; tout simplement un avertissement que l'on n'a pas encore utiliser en cas d'avoir une bonne connexion.


  • Joanna CarterJoanna Carter Membre, Modérateur

    En plus, pourquoi "dbQueue" ? C'est désormais "connexion" n'est-ce pas ?


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #45

    je pensais que le "rethrows" étant important, par rapport à  un "throws" que je connais bien... 


     


    bref, tout fonctionne très bien maintenant, juste 2 warning pour cause de 2 variables non (encore) utilisé.


     


    et oui, tout à  fait pour le moment je ne fais que de me connecter :)


    donc, j'ai bien le message console 



    bonne connexion


    et dans les annotations de mon code, je me suis planté ! 



    // pour le moment je ne me sert pas de cette fonction ! :)
    mutating func fetchTableMetadata()throws
    {
    guard let connexion = self.connexion else
    {
    throw ConnexionError.dbQueueError
    }

    // ...
    }
  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #46

    :D   :D   :D


     


    Prochain pas...


     


    Auparavant, tu avais du code pour récupérer les métadonnées ;



    {
    let tables = try dbQueue.inDatabase
    {
    db in

    try Row.fetchAll(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")
    }

    for row in tables
    {
    let table: String = row.value(named: "name")

    let tables_data = try dbQueue.inDatabase
    {
    db in

    try Row.fetchAll(db, "SELECT * FROM \(table)")
    }

    self.resultSet = tables_data
    }
    }
    catch
    {
    print(error)
    }

    Si je ne me trompes pas, tu itères sur les noms des tables, et à  l'intérieur de cette boucle, tu récupères les données par les colonnes là  dedans. Mais tu ne stockes pas les noms des tables. Du coup, tu auras, seulement, une list des colonnes sans aucune relation avec leurs table.


     


    Je crois que, à  la place d'une liste des métadonnées des colonnes tu devrais avoir un dictionnaire avec, pour les clés, les noms des tables et, pour les valeurs, les listes des métadonnées.


     


    Du coup :



    private var _resultSet: [String : [Row]]?

    internal private(set) var resultSet: [String : [Row]]?
    {
    get
    {
    return _resultSet
    }
    mutating set
    {
    _resultSet = newValue
    }
    }

    Et, pour le code de récupération, quelque chose comme ci : 



    guard var resultSet = self.resultSet else
    {
    return
    }

    for row in tables
    {
    let tableName: String = row.value(named: "name")

    let tableData = try dbQueue.inDatabase
    {
    db in

    try Row.fetchAll(db, "SELECT * FROM \(table)")
    }

    resultSet[tableName] = Array(tableData) // ou equivalent
    }
  • toolsDevtoolsDev Membre
    janvier 2017 modifié #47

    oui tout à  fait ça.


     


    je vais préparer la bonne méthode et je la post :)


     


    Edit :



    mutating func fetchTableMetadata()throws
    {
    guard let connexion = self.connexion else
    {
    throw ConnexionError.dbQueueError
    }
    do
    {
    // récupère toutes les tables :
    let tables = try connexion.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")


    let mutableArray:[String:Row] = []
    for table in tables
    {
    // récupère toutes les données de chaque tables :
    let metaDataTables = try connexion.prepare("SELECT * FROM \(table)")

    for metaDataTable in metaDataTables
    {
    //mutableArray[table] = metaDataTable
    print(metaDataTable)
    }

    print(mutableArray)
    }
    }
    catch
    {
    return print(error)
    }
    }


    Edit:


    par contre j'ai un retour de ma requête sur la console de debug avec une structure pas propre :



    Exemple de la documentation:
    let stmt = try db.prepare("SELECT id, email FROM users")
    for row in stmt {
    for (index, name) in stmt.columnNames.enumerate() {
    print ("\(name)=\(row[index]!)")
    // voici ce que la console affiche --> id: Optional(1), email: Optional("alice@mac.com")
    }
    }

    je ne trouve pas comment retiré ce superflu ?j'aimerais juste voir la table (exemple)"console"


    Du coup, je ne peu pas continuer mon autre requête avec ce genre de résultat.


  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #48

    Qu'est-ce que j'ai dit ? Petit pas  :-*



    do
    {
    let tables = try connexion.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")

    Je devine un peu, car je n'ai pas installé SQLite.swift, mais selon le code dans Statement.swift, on y trouvera une var columnNames qui est de type [String]. Du coup, il vaut mieux d'écrire :



    do
    {
    let tableStatement = try connexion.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")

    self.resultSet = [String : [String]]()

    for row in tableStatement
    {
    let tableName = row[0] as String

    var columnNames = [String]()

    let columnStatement = try connexion.prepare("SELECT * FROM \(tableName)")

    for columnName in columnStatement.columnNames
    {
    columnNames.append(columnName)
    }

    self.resultSet?[tableName] = columnNames
    }
    }

    Je ne l'ai pas passé par le compilateur mais il semble plus ou moins correct. Sinon, dis moi  ::)


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #49

    ;)


     


    alors je te remet le code complet :



    import Foundation
    import SQLite

    struct ConnexionSQLite
    {
    // Création d'une liste de type Error, pour personnaliser et afficher les erreurs de connexion:
    enum ConnexionError : Error
    {
    case dbQueueError
    }

    private var _resultSet: [String : [Row]]?

    internal private(set) var resultSet: [String : [Row]]?
    {
    get
    {
    return _resultSet
    }
    mutating set
    {
    _resultSet = newValue
    }
    }

    // Création du Singleton:
    static var sharedInstance: ConnexionSQLite? = ConnexionSQLite()


    // Création, test et affectation de la variable de connexion à  la base de données:
    private lazy var connexion: Connection? =
    {
    do
    {
    // Si le test de la connexion est réussis on renvoi connexion:
    let connexion = try Connection("games.sqlite3") // pour tester

    return connexion
    }
    catch
    {
    return nil
    }
    }()

    // A l'instanciation du Singleton on test si fetchTableMetadata est valide puis il est appelé:
    private init?()
    {
    do
    {
    try self.fetchTableMetadata()
    }
    catch
    {
    return nil
    }
    }

    mutating func fetchTableMetadata()throws
    {
    guard let connexion = self.connexion else
    {
    throw ConnexionError.dbQueueError
    }
    do
    {
    let tableStatement = try connexion.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")

    self.resultSet = [String : [String]]()

    for row in tableStatement
    {
    let tableName = row[0] as String // comme proposition de correction ? : let tableName = row[0] as! String

    var columnNames = [String]()

    let columnStatement = try connexion.prepare("SELECT * FROM \(tableName)")

    for columnName in columnStatement.columnNames
    {
    columnNames.append(columnName)
    }

    self.resultSet?[tableName] = columnNames
    }
    }
    catch
    {
    print(error)
    }
    }
    }

    j'ai deux erreurs à  la compilation :



    Cannot assign value of type '[String : [String]]' to type '[String : [Row]]?'

    'Binding?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

    donc un soucis de "optional" et un soucis de parse, si je dis pas de bêtises.


     


     


    Edit:


    en débogant je m'aperçois que je récupère bien toutes mes tables (sans le optional()) donc très bien :)


    mais arrivé ici :



    for columnName in columnStatement.columnNames
    {
    print(columnNames.append(columnName))
    }

    // j'ai tout une série de "()" vide...  

    tout est vide... donc je pense que le type est pas encore le bon .


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #50

    je re post, pour plus de clarté...


     


     


    Après avoir tout débogué tout fonctionne bien !


    le seul problème que je rencontre est que ceci :



    for columnName in columnStatement.columnNames
    {
    columnNames.append(columnName)
    }

    ramène uniquement les entêtes des des colonnes de mes tables


    donc, par exemple le schéma :


    Par table je récupère ceci :


    id


    name


    description


    date


    etc.


    donc, pas encore les données... :( pour remplir mon dictionary:


    resultSet[columnName] = les données


     


    de plus, le type de resultSet est faux :



    self.resultSet = [String : [Row]]()
    // ou j'ai encore testé ceci :
    self.resultSet = [String : [String]]()

  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #51

    Ah, tu voulais les données ! J'ai cru que tu voulais les métadonnées.


     


    Dans ce cas là , c'est le code dans les docs qu'il faut suivre :



    do
    {
    let tableStatement = try connexion.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")

    self.resultSet = [String : [Any]]()

    for row in tableStatement
    {
    let tableName = row[0] as String

    let columnStatement = try connexion.prepare("SELECT * FROM \(tableName)")

    var rowData = [Any]()

    for dataRow in columnStatement
    {
    for (index, _) in columnStatement.columnNames.enumerate()

    rowData.append(row[index])
    }

    self.resultSet?[tableName] = columnNames
    }
    }

    Et, il faut changer le type de resultSet et _resultSet vers [String : [Any]]


     


    Mais, attention ! Tu auras tous les données de plusieurs rangs pour chaque table, sans séparation entre les rangs.


     




    de plus, le type de resultSet est faux :



    self.resultSet = [String : [Row]]()
    // ou j'ai encore testé ceci :
    self.resultSet = [String : [String]]()



     


     


    Bah ouais ! Le type Row n'est pas utilisé dans cet exemple ; c'était du projet avec GRDB. Et, comme j'ai dit ci-dessus, il faut le changer encore une fois  8--)


  • bonjour Joanna Carter,


    Et bien en fait ce n'est pas une si mauvaise idée :) de plus tu m'as devancé, j'étais en train justement de fouiller 


    je prends les header (que tu m'as fait stocker via columnNames.append(columnName)


    Puis, je les stocke dans un nouveau dictionary, ayant pour key:value :


    header-->data (soit les info de mes tables)


    niveau conception je ne sais pas ce que tu en penses ? ça me parait propre.

  • Joanna CarterJoanna Carter Membre, Modérateur
    janvier 2017 modifié #53

    J'ai pu comprendre pourquoi tu voulais récupérer les métadonnées d'une BDD mais, récupérer les données, toutes en même temps ? ça me surprendrait.


     


    Tu peux nous dire le but de cet exercice ? Qu'est-ce que tu comptes faire avec les données ? Les montrer dans une TableView ?


     


    Surtout avec iOS, on est limité en mémoire disponible et, si le système d'exploitation marque une grimpe assez importante, il arrêtera l'appli. Du coup, il ne faut q'acquérir le minimum des données à  une seule fois.


     


    Normalement, pour montrer les données d'une BDD sans CoreData, il faut :


     


    1. récupérer le nombre de rangs dans la table (pour dire à  la TableView combien en total)


     


    2. récupérer assez de rangs de la BDD pour remplir les rangs visible dans la TableView, en même temps notant l'index du dernier rang, afin que l'on puisse l'utiliser pour la prochaine requête avec quelque chose comme - maTable.limit(nombre, offset: dernierIndex)


     


    En gros, je crois que tu as commencé à  réinventer la roue des frameworks comme CoreData  ???  :-*


     


    Est-ce que tu as une bonne raison pour choisir d'accéder une BDD direct, au lieu d'utiliser un framework déjà  écrit ?


  • toolsDevtoolsDev Membre
    janvier 2017 modifié #54

    Alors concernant coreData, je ne connais pas du tout son fonctionnement, mais (peut être à  tort) je pensais qu'il n'utilisait pas de base de données en SQLite3 (ou autre peu importe).


     


    Pour faire simple, est ce que je ne pourrais pas utiliser ma base de données.SQLite3 via CoreData ?


     


    Voici le détail de mon appli :


     


    une base de données.SQLite3 contenant comme Tables :


     


    jeux vidéos


       id,nom, date etc


    genres


          id,nom


    plateforme


       id,nom, date etc


    etc.


     


    et je souhaiterais tout récupérer au lancement de l'application, y affecter dans un tableau pour ensuite l'itérer pour effectivement :


     


    1) alléger la ressource (en affectant à  un tableau toutes les valeurs de mes tables, pour les itéré ensuite via des boucles et seulement à  la demande)


    2) remplir des tableView


    et passer les data de vues en vues (mais ça, ce n'est pas pour maintenant)


    et par la suite une synchronisation (de cette base embarqué) via un web service sur une autre base (backup)


     


    Voilà  vous savez tout ;)


     


    Pour te répondre, je n'es aucune restriction ou réticence à  utiliser tel ou tel outils.(j'ai suivi les conseils donnés dans mon autre poste, par des membres bien plus au fait que moi) C'est juste que je ne sais pas quelle façon de faire utiliser.


     


    je suis parti sur un concept que je connais : une base SQLite/SQL.


     


    Après, je me suis peut être planté dans ma façon de concevoir ?...


  • du coupe je ne sais pas trop si je cloture ce post, dite moi :)


  • Joanna CarterJoanna Carter Membre, Modérateur


    Alors concernant coreData, je ne connais pas du tout son fonctionnement, mais (peut être à  tort) je pensais qu'il n'utilisait pas de base de données en SQLite3 (ou autre peu importe).


     


    Pour faire simple, est ce que je ne pourrais pas utiliser ma base de données.SQLite3 via CoreData ?




     


    Avec Core Data, les données peut être stockées dans : XML, format binaire, SQLite ou seulement en mémoire https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/PersistentStoreFeatures.html


     


    Core Data commence avec le modèle des objets et c'est Core Data qui s'occupe de faire la base de données. Il n'est pas possible d'utiliser une BDD existante. En disant ça, c'est bien possible, après avoir créé le modèle, de faire une appli avec laquelle on peut transférer les données.


     




    Voici le détail de mon appli :


     


    une base de données.SQLite3 contenant comme Tables :


     


    jeux vidéos


       id,nom, date etc


    genres


          id,nom


    plateforme


       id,nom, date etc


    etc.


     


    et je souhaiterais tout récupérer au lancement de l'application, y affecter dans un tableau pour ensuite l'itérer pour effectivement :


     


    1) alléger la ressource (en affectant à  un tableau toutes les valeurs de mes tables, pour les itéré ensuite via des boucles et seulement à  la demande)


    2) remplir des tableView


    et passer les data de vues en vues (mais ça, ce n'est pas pour maintenant)


    et par la suite une synchronisation (de cette base embarqué) via un web service sur une autre base (backup)


     


    Voilà  vous savez tout ;)


     


    Pour te répondre, je n'es aucune restriction ou réticence à  utiliser tel ou tel outils.(j'ai suivi les conseils donnés dans mon autre poste, par des membres bien plus au fait que moi) C'est juste que je ne sais pas quelle façon de faire utiliser.


     


    je suis parti sur un concept que je connais : une base SQLite/SQL.


     


    Après, je me suis peut être planté dans ma façon de concevoir ?...




     


     


    On a déjà  parlé du concept d'un ORM et, bien, on pourrait se lancer en la création d'une telle bête ; mais, crois moi, c'est pas facile.


     


    En fait, j'ai commencé à  jouer avec et développer les ORMs il y a presque 20 ans (en Delphi) ; j'étais une des premières de les concevoir.


     


    Crois moi - c'est dur !!!


     


    Côté traduction des objet ver les tables, ça c'est assez facile mais, côté traductions des relations entre les objets vers les relations entre les tables, ça c'est vraiment sioux.


     


    Si on voulais modeler une relation comme Facture -> Lignes de facture :


     


    En objets c'est



    class LigneDeFacture
    {
    var article: Article

    var quantite: Int

    var prixUnite: Double

    var montant: Double
    }

    class Facture
    {
    var date: Date

    var client: Client

    var lignes: [LigneDeFacture]

    var montant: Double
    {
    // renvoyer total des montants des lignes
    }
    }

    ... mais avec une BDD, on parlerait de



    Table LigneDeFacture
    {
    id: Int

    factureID: Int

    articleID: Int

    quantite: Int

    prixUnite: Double

    montant: Double
    }

    class Facture
    {
    id: Int

    date: Date

    clientID: Int

    montant: Double // calculé des montants des lignes
    }

    C'est le boulot d'un ORM de "traduire" cette différence entre telles structures objet et telles structures BDD.


     


    Un ORM doit connaà®tre comment écrire le SQL pour gérer le CRUD (création / récupération / mis à  jour / suppression) des objets ; n'oubliant pas les suppressions en cascade.


     


    Core Data s'en occupe pas mal, mais il faut apprendre comment ça marche. En tout cas, là , si on ne choisissait pas le stockage SQLite, on serait obligé de charger toutes les données en même temps - ce qui n'est pas bon pour la consommation de mémoire sur un iBidule.

  • toolsDevtoolsDev Membre
    février 2017 modifié #57

    Bonjour,


     


    oui, donc on a bien fait de me conseiller GRDB.swift ou encore SQLite.swift (enfin ce dernier, non, je le déteste !) !


     


    je vais pas me prendre la tête, car l'a honnêtement tout ça pour ça... cela prend une ampleur incroyable !


     


    Je veux dire par là , que je souhaite simplement afficher des requêtes dans des tableView(et je me répète désolé tout ça est de la re, re dite...)


     


    Et en toute honnêteté je ne pige pas pourquoi SQLite.swift est si complexe sans raison.


    CoreData j'en veux pas, si je ne peux même pas maitrise mes requêtes et surtout utilisées une base déjà  existante, je ne comprends même pas l'intérêt de ce Framework...


    Exemple, je n'arrive toujours pas a afficher les données de mes tables sans ces optional() qui entoure chaque résulta de requêtes.


     


    Je t'avoue que je suis à  deux doigts de tout supprimer là  !


    Le pire c'est que je sais quoi faire, il y a tellement de parse à  faire, je ne pige vraiment pas pourquoi ce n'est pas plus userFriendly...


    Ce n'est pas du tout l'idée que je m'étais fait d'un petit projet tout bête.


    Le fait de changer deux fois de Framework n'arrange rien, je dois en prendre un et m'y tenir et je pense fuir SQLite.swift qui est horrible, ou alors je n'ai rien pigé...


    :(


     


     


    Edit:


    De plus, je ne veux pas vous déranger... quand je vois l'ampleur de mon post, pour pas de résulta, vous allez vous ennuyer 


  • Non n'abandonne pas !
  • toolsDevtoolsDev Membre
    février 2017 modifié #59
    Lu,

    Non, bien sur je suis tenace ! Demain from scratch sur du GRDB.Swif !

    La nuit porte conseil...


    Je dois comprendre ces maudits optional(), peut importe le Framework, ce n'est pas encore clair pour moi, mais via SQLite.swif c'est trop infernal...

    Sinon, j'ai des soucis de compréhension pour les types des dictionary, il faut que j'étudie tout ça.


    Merci beaucoup pour tes encouragements
  • Je te conseille d'isoler des sous-problèmes (par exemple sur les optional ou sur les dico) et d'ouvrir des nouvelles questions sur le forums. Regarde aussi un tuto sur les playgrounds Swift si tu ne connais pas.


    Bon courage,

    Je peux t'assurer que ce que tu cherches à  faire n'est pas compliqué et que tu seras très content d'avoir peiné puis compris.


    Question:

    Combien y a-t-il de tables dans ta BDD ?

    Combien y a-t-il de lignes dans la plus grosse à  peu près ?

    Qui met à  jour la table (il me semble que ton appli ne fait que lire la BDD) ?

    Quelle est la fréquence de write à  peu près? (Tous les jours ? Semaine? Mois ?)



    Sinon, CoreData et Realm c'est hyper classe, mais pour l'instant et pour ce que tu cherches à  faire, SQL semble très adapté. Enfin, ça dépend de tes réponses aux questions ci-dessus.
  • toolsDevtoolsDev Membre
    février 2017 modifié #61
    La faire fonctionner no problème

    Mais je voulais la faire dans les règles de l'art... Et j'ai pas super confiance en moi niveau conception de mes algo etc.

    Bref, tout ça pour dire qu'elle pourrait être déjà  faite, mais sûrement dégueux et coûteuse en ressources.


    Pour le moment j'ai 5 tables, sans compter 2 ou 3 tables de liaison (mais pas pour le moment, je gérerais via du code peut être)

    La plus grosse table 1000 lignes.

    La fréquence des mises a jour est encore à  déterminer, mais via l'utilisateur en manuel, une synchronisation via web service sur une base MySql.(bon, ça pour le moment pas important)


    Etffectivement, dans mon cas, connaissant un peu SQL/SQLIte3, je pense que c'est le plus simple/adapté pour mon utilisation, hors de question de ne pas pouvoir gérer moi même mes requêtes...(à‰dit: je parle pas de CoreData là , mais bien de GDBR.swift qui me semble plus simple, open ett userFriendly 😅)


    Merci 👍
Connectez-vous ou Inscrivez-vous pour répondre.