diff options
author | Simon Hafner <hafnersimon@gmail.com> | 2015-03-05 14:44:54 -0600 |
---|---|---|
committer | Simon Hafner <hafnersimon@gmail.com> | 2015-03-05 14:44:54 -0600 |
commit | 70eaf92ff0493ed11c6e6c8887bca1b235371aff (patch) | |
tree | 530255b69df349728c76621e97bf40a11d63fe79 /examples/cross_todo/nim_backend/backend.nim | |
parent | f592240c545506448e2bea78a2fa3404c7f46e69 (diff) | |
parent | 8f43979cf6308c9d7e14a0d87c0faf227e1c4afe (diff) | |
download | Nim-70eaf92ff0493ed11c6e6c8887bca1b235371aff.tar.gz |
Merge branch 'devel' into warning-for-result
Diffstat (limited to 'examples/cross_todo/nim_backend/backend.nim')
-rw-r--r-- | examples/cross_todo/nim_backend/backend.nim | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/examples/cross_todo/nim_backend/backend.nim b/examples/cross_todo/nim_backend/backend.nim new file mode 100644 index 000000000..5b49bc4a9 --- /dev/null +++ b/examples/cross_todo/nim_backend/backend.nim @@ -0,0 +1,219 @@ +# Backend for a simple todo program with sqlite persistence. +# +# Most procs dealing with a TDbConn object may raise an EDb exception. + +import db_sqlite, parseutils, strutils, times + + +type + TTodo* = object + ## A todo object holding the information serialized to the database. + id: int64 ## Unique identifier of the object in the + ## database, use the getId() accessor to read it. + text*: string ## Description of the task to do. + priority*: int ## The priority can be any user defined integer. + isDone*: bool ## Done todos are still kept marked. + modificationDate: Time ## The modification time can't be modified from + ## outside of this module, use the + ## getModificationDate accessor. + + TPagedParams* = object + ## Contains parameters for a query, initialize default values with + ## initDefaults(). + pageSize*: int64 ## Lines per returned query page, -1 for + ## unlimited. + priorityAscending*: bool ## Sort results by ascending priority. + dateAscending*: bool ## Sort results by ascending modification date. + showUnchecked*: bool ## Get unchecked objects. + showChecked*: bool ## Get checked objects. + + +# - General procs +# +proc initDefaults*(params: var TPagedParams) = + ## Sets sane defaults for a TPagedParams object. + ## + ## Note that you should always provide a non zero pageSize, either a specific + ## positive value or negative for unbounded query results. + params.pageSize = high(int64) + params.priorityAscending = false + params.dateAscending = false + params.showUnchecked = true + params.showChecked = false + + +proc openDatabase*(path: string): TDbConn = + ## Creates or opens the sqlite3 database. + ## + ## Pass the path to the sqlite database, if the database doesn't exist it + ## will be created. The proc may raise a EDB exception + let + conn = db_sqlite.open(path, "user", "pass", "db") + query = sql"""CREATE TABLE IF NOT EXISTS Todos ( + id INTEGER PRIMARY KEY, + priority INTEGER NOT NULL, + is_done BOOLEAN NOT NULL, + desc TEXT NOT NULL, + modification_date INTEGER NOT NULL, + CONSTRAINT Todos UNIQUE (id))""" + + db_sqlite.exec(conn, query) + result = conn + + +# - Procs related to TTodo objects +# +proc initFromDB(id: int64; text: string; priority: int, isDone: bool; + modificationDate: Time): TTodo = + ## Returns an initialized TTodo object created from database parameters. + ## + ## The proc assumes all values are right. Note this proc is NOT exported. + assert(id >= 0, "Identity identifiers should not be negative") + result.id = id + result.text = text + result.priority = priority + result.isDone = isDone + result.modificationDate = modificationDate + + +proc getId*(todo: TTodo): int64 = + ## Accessor returning the value of the private id property. + return todo.id + + +proc getModificationDate*(todo: TTodo): Time = + ## Returns the last modification date of a TTodo entry. + return todo.modificationDate + + +proc update*(todo: var TTodo; conn: TDbConn): bool = + ## Checks the database for the object and refreshes its variables. + ## + ## Use this method if you (or another entity) have modified the database and + ## want to update the object you have with whatever the database has stored. + ## Returns true if the update succeeded, or false if the object was not found + ## in the database any more, in which case you should probably get rid of the + ## TTodo object. + assert(todo.id >= 0, "The identifier of the todo entry can't be negative") + let query = sql"""SELECT desc, priority, is_done, modification_date + FROM Todos WHERE id = ?""" + + try: + let rows = conn.getAllRows(query, $todo.id) + if len(rows) < 1: + return + assert(1 == len(rows), "Woah, didn't expect so many rows") + todo.text = rows[0][0] + todo.priority = rows[0][1].parseInt + todo.isDone = rows[0][2].parseBool + todo.modificationDate = Time(rows[0][3].parseInt) + result = true + except: + echo("Something went wrong selecting for id " & $todo.id) + + +proc save*(todo: var TTodo; conn: TDbConn): bool = + ## Saves the current state of text, priority and isDone to the database. + ## + ## Returns true if the database object was updated (in which case the + ## modification date will have changed). The proc can return false if the + ## object wasn't found, for instance, in which case you should drop that + ## object anyway and create a new one with addTodo(). Also EDb can be raised. + assert(todo.id >= 0, "The identifier of the todo entry can't be negative") + let + currentDate = getTime() + query = sql"""UPDATE Todos + SET desc = ?, priority = ?, is_done = ?, modification_date = ? + WHERE id = ?""" + rowsUpdated = conn.execAffectedRows(query, $todo.text, + $todo.priority, $todo.isDone, $int(currentDate), $todo.id) + + if 1 == rowsUpdated: + todo.modificationDate = currentDate + result = true + + +# - Procs dealing directly with the database +# +proc addTodo*(conn: TDbConn; priority: int; text: string): TTodo = + ## Inserts a new todo into the database. + ## + ## Returns the generated todo object. If there is an error EDb will be raised. + let + currentDate = getTime() + query = sql"""INSERT INTO Todos + (priority, is_done, desc, modification_date) + VALUES (?, 'false', ?, ?)""" + todoId = conn.insertId(query, priority, text, $int(currentDate)) + + result = initFromDB(todoId, text, priority, false, currentDate) + + +proc deleteTodo*(conn: TDbConn; todoId: int64): int64 {.discardable.} = + ## Deletes the specified todo identifier. + ## + ## Returns the number of rows which were affected (1 or 0) + let query = sql"""DELETE FROM Todos WHERE id = ?""" + result = conn.execAffectedRows(query, $todoId) + + +proc getNumEntries*(conn: TDbConn): int = + ## Returns the number of entries in the Todos table. + ## + ## If the function succeeds, returns the zero or positive value, if something + ## goes wrong a negative value is returned. + let query = sql"""SELECT COUNT(id) FROM Todos""" + try: + let row = conn.getRow(query) + result = row[0].parseInt + except: + echo("Something went wrong retrieving number of Todos entries") + result = -1 + + +proc getPagedTodos*(conn: TDbConn; params: TPagedParams; + page = 0'i64): seq[TTodo] = + ## Returns the todo entries for a specific page. + ## + ## Pages are calculated based on the params.pageSize parameter, which can be + ## set to a negative value to specify no limit at all. The query will be + ## affected by the TPagedParams, which should have sane values (call + ## initDefaults). + assert(page >= 0, "You should request a page zero or bigger than zero") + result = @[] + + # Well, if you don't want to see anything, there's no point in asking the db. + if not params.showUnchecked and not params.showChecked: return + + let + order_by = [ + if params.priorityAscending: "ASC" else: "DESC", + if params.dateAscending: "ASC" else: "DESC"] + + query = sql("""SELECT id, desc, priority, is_done, modification_date + FROM Todos + WHERE is_done = ? OR is_done = ? + ORDER BY priority $1, modification_date $2, id DESC + LIMIT ? * ?,?""" % order_by) + + args = @[$params.showChecked, $(not params.showUnchecked), + $params.pageSize, $page, $params.pageSize] + + #echo("Query " & string(query)) + #echo("args: " & args.join(", ")) + + var newId: BiggestInt + for row in conn.fastRows(query, args): + let numChars = row[0].parseBiggestInt(newId) + assert(numChars > 0, "Huh, couldn't parse identifier from database?") + result.add(initFromDB(int64(newId), row[1], row[2].parseInt, + row[3].parseBool, Time(row[4].parseInt))) + + +proc getTodo*(conn: TDbConn; todoId: int64): ref TTodo = + ## Returns a reference to a TTodo or nil if the todo could not be found. + var tempTodo: TTodo + tempTodo.id = todoId + if tempTodo.update(conn): + new(result) + result[] = tempTodo |