├── .gitattributes ├── LICENSE.txt ├── README.md ├── grasp ├── init.lua ├── init.moon ├── query.lua ├── query.moon ├── util.lua └── util.moon ├── rock.yml ├── rockspecs ├── grasp-1.1-1.rockspec ├── grasp-1.2-1.rockspec ├── grasp-1.2.1-1.rockspec ├── grasp-1.2.2-1.rockspec └── grasp-1.3-1.rockspec └── spec ├── grasp_spec.moon └── simple.moon /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lua linguist-generated 2 | *.rockspec linguist-generated 3 | *.mp linguist-language=MoonScript 4 | docs/* linguist-documentation 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grasp 2 | 3 | Grasp is a wrapper around the [LuaSQLite3](https://lua.sqlite.org/index.cgi/index) binding for sqlite3. Designed to be fairly similar to [Clutch](https://github.com/akojo/clutch), while having a more functional approach. 4 | 5 | Ok, I admit it, the only reason I made this is because Clutch wouldn't run on 5.1, ok?! Really, go use that instead, it's a genius library. I guess you can use this as a last resort or if you really, really hate yourself. 6 | 7 | As such, this README will be a close-looking copy of the clutch one, but in MoonScript, and with my syntax. 8 | 9 | ## Table of contents 10 | 11 | - [Grasp](#grasp) 12 | - [Table of contents](#table-of-contents) 13 | - [Opening a database](#opening-a-database) 14 | - [Querying the database](#querying-the-database) 15 | - [`squery`](#squery) 16 | - [Binding parameters](#binding-parameters) 17 | - [Named parameters](#named-parameters) 18 | - [Anonymous/positional parameters](#anonymouspositional-parameters) 19 | - [Updating the database](#updating-the-database) 20 | - [Preparing statements](#preparing-statements) 21 | - [Resets](#resets) 22 | - [Transactions](#transactions) 23 | - [Error handling](#error-handling) 24 | - [Query building](#query-building) 25 | - [Supported statements](#supported-statements) 26 | - [explain](#explain) 27 | - [Transactions and Savepoints](#transactions-and-savepoints) 28 | - [create](#create) 29 | - [insert and replace](#insert-and-replace) 30 | - [select](#select) 31 | - [delete](#delete) 32 | - [drop](#drop) 33 | - [update](#update) 34 | - [Installing](#installing) 35 | - [Tests](#tests) 36 | - [License](#license) 37 | 38 | ## Opening a database 39 | 40 | ```moon 41 | import Database from require "grasp" 42 | db = Database "my.db" 43 | ``` 44 | 45 | It optionally takes a table of attributes, defined like this: 46 | 47 | ``` 48 | readonly / ro :: boolean 49 | readwrite / rw :: boolean 50 | create :: boolean 51 | uri :: boolean 52 | memory :: boolean 53 | mutex :: boolean 54 | cache :: string [shared|private] 55 | volatile :: boolean 56 | ``` 57 | 58 | All of them, except for `volatile`, correspond to a `SQLITE_OPEN_*` open [flag](https://www.sqlite.org/c3ref/open.html). I think it's pretty easy to figure it out. `volatile` will remove the file (if it is not `:memory:`) on close. The default attributes table is `create=true, rw=true`. 59 | 60 | As with Clutch, the filename `:memory:` will open an in-memory volatile database. This doesn't have anything to do with the `volatile` attribute. An empty filename will create a temporal on-disk database with `volatile` set to true. It uses `os.tmpname` to get a temporal filename, and you can get it with `db.filename`. 61 | 62 | ## Querying the database 63 | 64 | You use the `query`, `query1` and `iquery` functions to make a query to the database. They're all iterators, but they iterate in different ways. Here are the signatures: 65 | 66 | ``` 67 | query :: Database -> (string, table) -> (_ -> {string:_}) 68 | query1 :: Database -> (string, table) -> (_ -> [_]) 69 | iquery :: Database -> (string, table) -> (_ -> ...) 70 | ``` 71 | 72 | This might mean nothing to you, so let's see a practical example: 73 | 74 | ```moon 75 | import query, query1, iquery from require "grasp" 76 | for row in (query db) "select * from t" 77 | print row.col1, row.col2 78 | 79 | for row in (query1 db) "select * from t" 80 | print row[1], row[2] 81 | 82 | for col1, col2 in (iquery db) "select * from t" 83 | print col1, col2 84 | ``` 85 | 86 | These are all equivalent, so it's all up to preference. There's `queryone` and `queryall` shorthands too, but they might behave a bit differently. 87 | 88 | - `queryone db sql`: returns the first row out of all results. 89 | - `queryall db sql`: returns a table of all rows returned. 90 | 91 | ### `squery` 92 | 93 | As of Grasp 1.3, there is a new function `squery` which works similarly to `queryall`, but if no results were returned it will: 94 | 95 | - Return `{affected_rows: n}` if there's any affected rows. 96 | - Return `true` otherwise. 97 | 98 | This is pretty much made to mimic `query` in [pgmoon](https://github.com/leafo/pgmoon), since I might be using Grasp to write a Lapis backend for SQLite. 99 | 100 | ## Binding parameters 101 | 102 | ### Named parameters 103 | 104 | The `query*` functions take an optional table to provide parameters. It uses prepare/bind functions internally so you can use `:`, `$` and `@` just as you would with Clutch and sqlite3. 105 | 106 | ```moon 107 | (query db) "select * from t where value = :value", value: "example" 108 | ``` 109 | 110 | ### Anonymous/positional parameters 111 | 112 | Just sqlite3's `?` and `?n`. 113 | 114 | ```moon 115 | (query db) "select * from t where value = ?", {"example"} 116 | (query db) "select * from t where value = ?1 eulav = ?2", {"example", "elpmaxe"} 117 | ``` 118 | 119 | ## Updating the database 120 | 121 | `update(db)(sql)` is your function to pipe straight SQL to your database. It's signature is `update :: Database -> (string, table) -> (boolean, number)`, where the first string is the query, and it returns both a boolean (`sqlite.DONE` or not) and the result code. 122 | 123 | It also prepares the statement behind the scenes, so you can pass a table to bind arguments. 124 | 125 | ## Preparing statements 126 | 127 | This library also lets you prepare statements manually via `Statement`, which has the signature `Statement :: Database -> string -> Statement`. You can know the SQL you passed to it with `stmt.sql`. 128 | 129 | ```moon 130 | import Statement from require "grasp" 131 | 132 | stmt = Statement "select * from t where value = :value" 133 | ``` 134 | 135 | You can then call `query*` functions with an extra bindtable argument. 136 | 137 | ```moon 138 | (query stmt) value: "example" 139 | ``` 140 | 141 | Of course, you can also bind them manually, with the several `bind*` functions: 142 | 143 | ``` 144 | bind :: Statement -> table -> (boolean, number) 145 | bindOne :: Statement -> (number|string, _) -> (boolean, number) 146 | bindMany :: Statement -> [_] 147 | ``` 148 | 149 | Which is better explained visually like this: 150 | 151 | ```moon 152 | import bind, bindOne, bindMany from require "grasp" 153 | (bind stmt) value: "example" 154 | (bindOne stmt) "value", "example" 155 | (bindMany stmt) 1, 2, 3, 4, 5 156 | ``` 157 | 158 | All about preference! 159 | 160 | ### Resets 161 | 162 | Similarly to Clutch, calling `execute` and `query*` functions on a statement will cause it to be reset. 163 | 164 | ## Transactions 165 | 166 | The `Transaction` method takes a function which will run inside a transaction, built using savepoints. A very graphical example: 167 | 168 | ```moon 169 | import Transaction from require "grasp" 170 | (Transaction db) => 171 | (update @) "some sql statement" 172 | (update @) "another sql statement" 173 | ``` 174 | 175 | ## Error handling 176 | 177 | Unlike Clutch, this will not error on user-called functions, but instead return a boolean status and the error code. 178 | 179 | ## Query building 180 | 181 | Grasp 1.2 implements a query builder for SQL. It ain't much, but it's honest work. You use it by importing the `sql` function in `grasp.query`. It takes a function, and a lot of magic happens there, just see for yourself! 182 | 183 | ```moon 184 | sql -> 185 | create "tbl", -> columns: 186 | ee: "TEXT NOT NULL" 187 | ``` 188 | 189 | ### Supported statements 190 | 191 | #### explain 192 | 193 | Takes any SQL builder, and precedes it with `EXPLAIN` or `EXPLAIN QUERY PLAN`. 194 | 195 | ```moon 196 | sql -> explain queryplan, -> ... 197 | sql -> explain -> ... 198 | ``` 199 | 200 | #### Transactions and Savepoints 201 | 202 | ```moon 203 | sql -> 204 | -- begin transaction 205 | begin! 206 | begin deferred 207 | begin immediate 208 | begin exclusive 209 | -- rollback transaction 210 | rollback! 211 | -- end transaction 212 | commit! 213 | End! 214 | 215 | sql -> 216 | -- savepoints 217 | savepoint "name" 218 | release "name" 219 | rollback "name 220 | ``` 221 | 222 | #### create 223 | 224 | Well, more like `CREATE TABLE`: 225 | 226 | ```moon 227 | sql -> create "tablename", -> 228 | temporary! -- TEMPORARY 229 | always! -- removes IF NOT EXISTS 230 | without_rowid! -- adds WITHOUT ROWID 231 | columns: 232 | whatever: "TEXT NOT NULL" -- and such 233 | ``` 234 | 235 | #### insert and replace 236 | 237 | `replace` works pretty much the same, but emitting `REPLACE` instead 238 | 239 | ```moon 240 | sql -> 241 | insert -> 242 | replace! -- OR REPLACE 243 | rollback! -- OR ROLLBACK 244 | abort! -- OR ABORT 245 | fail! -- OR FAIL 246 | ignore! -- OR IGNORE 247 | into "tablename" 248 | alias "whatever" -- AS whatever 249 | values: 250 | column: value 251 | -- alternatively 252 | insert into "tablename", -> values: 253 | column: value 254 | ``` 255 | 256 | #### select 257 | 258 | ```moon 259 | sql -> 260 | select "*", -> 261 | distinct! 262 | all! 263 | From "tablename" 264 | where "expr" 265 | where a: v -- WHERE a = v 266 | order: "expr" -- ORDER BY expr 267 | limit: "expr" -- LIMIT expr 268 | offset: "expr" -- OFFSET expr 269 | -- alternatively 270 | select "*", From "tablename", -> 271 | ``` 272 | 273 | #### delete 274 | 275 | ```moon 276 | sql -> 277 | delete -> 278 | From "tablename" 279 | where "expr" 280 | where a: v -- WHERE a = v 281 | -- alternatively 282 | delete From "tablename", -> where a: v 283 | ``` 284 | 285 | #### drop 286 | 287 | ```moon 288 | sql -> 289 | drop "tablename" 290 | drop "tablename", -> ifexists! 291 | ``` 292 | 293 | #### update 294 | 295 | ```moon 296 | sql -> 297 | update "tablename", -> 298 | where id: 1 299 | values: 300 | login: "user" 301 | email: "ex@example.com" 302 | ``` 303 | 304 | ## Installing 305 | 306 | You can get Grasp on LuaRocks: 307 | 308 | ```sh 309 | $ luarocks install grasp 310 | ``` 311 | 312 | If building from source: 313 | ```sh 314 | $ moonc grasp 315 | $ luarocks make 316 | ``` 317 | 318 | ## Tests 319 | 320 | You can run the tests with `busted` (you will need MoonScript). 321 | 322 | ``` 323 | $ luarocks install busted 324 | $ luarocks install moonscript 325 | $ busted 326 | ``` 327 | 328 | ## License 329 | 330 | `lsqlite3` uses the MIT license. Grasp is released onto the public domain. 331 | 332 | ``` 333 | This is free and unencumbered software released into the public domain. 334 | 335 | Anyone is free to copy, modify, publish, use, compile, sell, or 336 | distribute this software, either in source code form or as a compiled 337 | binary, for any purpose, commercial or non-commercial, and by any 338 | means. 339 | 340 | In jurisdictions that recognize copyright laws, the author or authors 341 | of this software dedicate any and all copyright interest in the 342 | software to the public domain. We make this dedication for the benefit 343 | of the public at large and to the detriment of our heirs and 344 | successors. We intend this dedication to be an overt act of 345 | relinquishment in perpetuity of all present and future rights to this 346 | software under copyright law. 347 | 348 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 349 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 350 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 351 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 352 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 353 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 354 | OTHER DEALINGS IN THE SOFTWARE. 355 | 356 | For more information, please refer to 357 | ``` -------------------------------------------------------------------------------- /grasp/init.lua: -------------------------------------------------------------------------------- 1 | local expect, typeset, typeof 2 | do 3 | local _obj_0 = require("grasp.util") 4 | expect, typeset, typeof = _obj_0.expect, _obj_0.typeset, _obj_0.typeof 5 | end 6 | local sqlite = require("lsqlite3") 7 | local unpack = unpack or table.unpack 8 | local OPEN_READONLY = sqlite.OPEN_READONLY 9 | local OPEN_READWRITE = sqlite.OPEN_READWRITE 10 | local OPEN_CREATE = sqlite.OPEN_CREATE 11 | local OPEN_URI = sqlite.OPEN_URI 12 | local OPEN_MEMORY = sqlite.OPEN_MEMORY 13 | local OPEN_NOMUTEX = sqlite.OPEN_NOMUTEX 14 | local OPEN_FULLMUTEX = sqlite.OPEN_FULLMUTEX 15 | local OPEN_SHAREDCACHE = sqlite.OPEN_SHAREDCACHE 16 | local OPEN_PRIVATECACHE = sqlite.OPEN_PRIVATECACHE 17 | local OK = sqlite.OK 18 | local changesIn 19 | local Database 20 | Database = function(filename, attr) 21 | if attr == nil then 22 | attr = { 23 | create = true, 24 | rw = true 25 | } 26 | end 27 | expect(1, filename, { 28 | "string" 29 | }) 30 | expect(2, attr, { 31 | "table" 32 | }) 33 | if filename == ":memory:" then 34 | return typeset({ 35 | filename = filename, 36 | db = sqlite.open_memory(), 37 | attributes = attr 38 | }, "Database") 39 | end 40 | if filename == "" then 41 | filename = os.tmpname() 42 | attr.volatile = true 43 | end 44 | local flags = 0 45 | if attr.readonly or attr.ro then 46 | flags = flags + OPEN_READONLY 47 | end 48 | if attr.readwrite or attr.rw then 49 | flags = flags + OPEN_READWRITE 50 | end 51 | if attr.create then 52 | flags = flags + OPEN_CREATE 53 | end 54 | if attr.uri then 55 | flags = flags + OPEN_URI 56 | end 57 | if attr.memory then 58 | flags = flags + OPEN_MEMORY 59 | end 60 | if not attr.mutex then 61 | flags = flags + OPEN_NOMUTEX 62 | end 63 | if attr.mutex then 64 | flags = flags + OPEN_FULLMUTEX 65 | end 66 | if attr.cache == "shared" then 67 | flags = flags + OPEN_SHAREDCACHE 68 | end 69 | if attr.cache == "private" then 70 | flags = flags + OPEN_PRIVATECACHE 71 | end 72 | local db, code, msg = sqlite.open(filename, flags) 73 | if not (db) then 74 | return nil, code, msg 75 | end 76 | return typeset({ 77 | filename = filename, 78 | db = db, 79 | attributes = attr 80 | }, "Database") 81 | end 82 | local Statement 83 | Statement = function(self) 84 | expect(1, self, { 85 | "Database" 86 | }) 87 | return function(sql) 88 | expect(2, sql, { 89 | "string" 90 | }) 91 | if not (sql:match(";$")) then 92 | sql = sql .. ";" 93 | end 94 | if not (sqlite.complete(sql)) then 95 | error("Not a valid SQL statement: [[" .. tostring(sql) .. "]]") 96 | end 97 | local stat = self.db:prepare(sql) 98 | if "userdata" ~= typeof(stat) then 99 | error("Could not prepare statement: [[" .. tostring(sql) .. "]], (" .. tostring(stat) .. ")") 100 | end 101 | return typeset({ 102 | sql = sql, 103 | stat = stat, 104 | db = self 105 | }, "Statement") 106 | end 107 | end 108 | local finalize 109 | finalize = function(self) 110 | expect(1, self, { 111 | "Statement" 112 | }) 113 | local ok = self.stat:finalize() 114 | return (ok == OK), ok 115 | end 116 | local isOpen 117 | isOpen = function(self) 118 | expect(1, self, { 119 | "Statement" 120 | }) 121 | return self.stat:isopen() 122 | end 123 | local bind 124 | bind = function(self) 125 | expect(1, self, { 126 | "Statement" 127 | }) 128 | return function(nametable) 129 | expect(2, nametable, { 130 | "table" 131 | }) 132 | if not (self.stat) then 133 | print((require("inspect"))(self)) 134 | end 135 | local ok = self.stat:bind_names(nametable) 136 | return (ok == OK), ok 137 | end 138 | end 139 | local bindOne 140 | bindOne = function(self) 141 | expect(1, self, { 142 | "Statement" 143 | }) 144 | return function(n, value) 145 | expect(2, n, { 146 | "number", 147 | "string" 148 | }) 149 | local ok = self.stat:bind(n, value) 150 | return (ok == OK), ok 151 | end 152 | end 153 | local bindMany 154 | bindMany = function(self) 155 | expect(1, self, { 156 | "Statement" 157 | }) 158 | return function(list) 159 | local ok = self.stat:bind_values(unpack(list)) 160 | return (ok == OK), ok 161 | end 162 | end 163 | local query 164 | query = function(self) 165 | expect(1, self, { 166 | "Statement" 167 | }) 168 | return self.stat:nrows() 169 | end 170 | local query1 171 | query1 = function(self) 172 | expect(1, self, { 173 | "Statement" 174 | }) 175 | return self.stat:rows() 176 | end 177 | local iquery 178 | iquery = function(self) 179 | expect(1, self, { 180 | "Statement" 181 | }) 182 | return self.stat:urows() 183 | end 184 | local queryall 185 | queryall = function(self) 186 | expect(1, self, { 187 | "Statement" 188 | }) 189 | local r 190 | do 191 | local _accum_0 = { } 192 | local _len_0 = 1 193 | for row in self.stat:nrows() do 194 | _accum_0[_len_0] = row 195 | _len_0 = _len_0 + 1 196 | end 197 | r = _accum_0 198 | end 199 | self.stat:reset() 200 | return r 201 | end 202 | local queryone 203 | queryone = function(self) 204 | expect(1, self, { 205 | "Statement" 206 | }) 207 | local r = (queryall(self))[1] 208 | self.stat:reset() 209 | return r 210 | end 211 | local squery 212 | squery = function(self) 213 | expect(1, self, { 214 | "Statement" 215 | }) 216 | local r = queryall(self) 217 | self.stat:reset() 218 | if #r > 0 then 219 | return r 220 | else 221 | local changes = (changesIn(self.db)) 222 | if changes > 0 then 223 | return { 224 | affected_rows = changes 225 | } 226 | else 227 | return true 228 | end 229 | end 230 | end 231 | local execute 232 | execute = function(self) 233 | expect(1, self, { 234 | "Statement" 235 | }) 236 | local status = self.stat:step() 237 | self.stat:reset() 238 | return (status == sqlite.DONE), status 239 | end 240 | local close 241 | close = function(self) 242 | expect(1, self, { 243 | "Database" 244 | }) 245 | local ok = self.db:close() 246 | if ok and self.attributes.volatile and (filename ~= ":memory:") then 247 | os.remove(filename) 248 | end 249 | return (ok == OK), ok 250 | end 251 | local errorFor 252 | errorFor = function(self) 253 | expect(1, self, { 254 | "Database" 255 | }) 256 | return (self.db:errcode()), (self.db:errmsg()) 257 | end 258 | changesIn = function(self) 259 | expect(1, self, { 260 | "Database" 261 | }) 262 | return self.db:changes() 263 | end 264 | local allChangesIn 265 | allChangesIn = function(self) 266 | expect(1, self, { 267 | "Database" 268 | }) 269 | return self.db:total_changes() 270 | end 271 | local _Statement_isOpen = isOpen 272 | isOpen = function(self) 273 | if "Statement" == typeof(self) then 274 | return _Statement_isOpen(self) 275 | end 276 | expect(1, self, { 277 | "Database" 278 | }) 279 | return self.stat:isopen() 280 | end 281 | local update 282 | update = function(self) 283 | expect(1, self, { 284 | "Database" 285 | }) 286 | return function(sql, bindt) 287 | if bindt == nil then 288 | bindt = { } 289 | end 290 | expect(2, sql, { 291 | "string" 292 | }) 293 | expect(3, bindt, { 294 | "table" 295 | }) 296 | local stmt = (Statement(self))(sql) 297 | if not ((bind(stmt))(bindt)) then 298 | error("update : Failed to bind to [[" .. tostring(sql) .. "]]") 299 | end 300 | return execute(stmt) 301 | end 302 | end 303 | local _Statement_query = query 304 | query = function(self) 305 | if "Statement" == typeof(self) then 306 | return _Statement_query(self) 307 | end 308 | expect(1, self, { 309 | "Database" 310 | }) 311 | return function(sql, bindt) 312 | if bindt == nil then 313 | bindt = { } 314 | end 315 | expect(2, sql, { 316 | "string" 317 | }) 318 | expect(3, bindt, { 319 | "table" 320 | }) 321 | local stmt = (Statement(self))(sql) 322 | if not ((bind(stmt))(bindt)) then 323 | error("query : Failed to bind to [[" .. tostring(sql) .. "]]") 324 | end 325 | return query(stmt) 326 | end 327 | end 328 | local _Statement_query1 = query1 329 | query1 = function(self) 330 | if "Statement" == typeof(self) then 331 | return _Statement_query1(self) 332 | end 333 | expect(1, self, { 334 | "Database" 335 | }) 336 | return function(sql, bindt) 337 | if bindt == nil then 338 | bindt = { } 339 | end 340 | expect(2, sql, { 341 | "string" 342 | }) 343 | expect(3, bindt, { 344 | "table" 345 | }) 346 | local stmt = (Statement(self))(sql) 347 | if not ((bind(stmt))(bindt)) then 348 | error("query1 : Failed to bind to [[" .. tostring(sql) .. "]]") 349 | end 350 | return query1(stmt) 351 | end 352 | end 353 | local _Statement_iquery = iquery 354 | iquery = function(self) 355 | if "Statement" == typeof(self) then 356 | return _Statement_iquery(self) 357 | end 358 | expect(1, self, { 359 | "Database" 360 | }) 361 | return function(sql, bindt) 362 | if bindt == nil then 363 | bindt = { } 364 | end 365 | expect(2, sql, { 366 | "string" 367 | }) 368 | expect(3, bindt, { 369 | "table" 370 | }) 371 | local stmt = (Statement(self))(sql) 372 | if not ((bind(stmt))(bindt)) then 373 | error("iquery : Failed to bind to [[" .. tostring(sql) .. "]]") 374 | end 375 | return iquery(stmt) 376 | end 377 | end 378 | local _Statement_queryall = queryall 379 | queryall = function(self) 380 | if "Statement" == typeof(self) then 381 | return _Statement_queryall(self) 382 | end 383 | expect(1, self, { 384 | "Database" 385 | }) 386 | return function(sql, bindt) 387 | if bindt == nil then 388 | bindt = { } 389 | end 390 | expect(2, sql, { 391 | "string" 392 | }) 393 | expect(3, bindt, { 394 | "table" 395 | }) 396 | local stmt = (Statement(self))(sql) 397 | if not ((bind(stmt))(bindt)) then 398 | error("queryall : Failed to bind to [[" .. tostring(sql) .. "]]") 399 | end 400 | return queryall(stmt) 401 | end 402 | end 403 | local _Statement_queryone = queryone 404 | queryone = function(self) 405 | if "Statement" == typeof(self) then 406 | return _Statement_queryone(self) 407 | end 408 | expect(1, self, { 409 | "Database" 410 | }) 411 | return function(sql, bindt) 412 | if bindt == nil then 413 | bindt = { } 414 | end 415 | expect(2, sql, { 416 | "string" 417 | }) 418 | expect(3, bindt, { 419 | "table" 420 | }) 421 | local stmt = (Statement(self))(sql) 422 | if not ((bind(stmt))(bindt)) then 423 | error("queryone : Failed to bind to [[" .. tostring(sql) .. "]]") 424 | end 425 | return queryone(stmt) 426 | end 427 | end 428 | local _Statement_squery = squery 429 | squery = function(self) 430 | if "Statement" == typeof(self) then 431 | return _Statement_squery(self) 432 | end 433 | expect(1, self, { 434 | "Database" 435 | }) 436 | return function(sql, bindt) 437 | if bindt == nil then 438 | bindt = { } 439 | end 440 | expect(2, sql, { 441 | "string" 442 | }) 443 | expect(3, bindt, { 444 | "table" 445 | }) 446 | local stmt = (Statement(self))(sql) 447 | if not ((bind(stmt))(bindt)) then 448 | error("squery : Failed to bind to [[" .. tostring(sql) .. "]]") 449 | end 450 | return squery(stmt) 451 | end 452 | end 453 | local Transaction 454 | Transaction = function(self) 455 | expect(1, self, { 456 | "Database" 457 | }) 458 | local upd = update(self) 459 | return function(fn) 460 | expect(2, fn, { 461 | "function" 462 | }) 463 | if not (upd("SAVEPOINT grasp_savepoint")) then 464 | error("Could not start transaction (grasp_savepoint)") 465 | end 466 | local ok = pcall(function() 467 | return fn(self) 468 | end) 469 | if ok then 470 | upd("RELEASE grasp_savepoint") 471 | else 472 | upd("ROLLBACK TO grasp_savepoint") 473 | end 474 | return ok 475 | end 476 | end 477 | return { 478 | OPEN_CREATE = OPEN_CREATE, 479 | OPEN_FULLMUTEX = OPEN_FULLMUTEX, 480 | OPEN_MEMORY = OPEN_MEMORY, 481 | OPEN_NOMUTEX = OPEN_NOMUTEX, 482 | OPEN_PRIVATECACHE = OPEN_PRIVATECACHE, 483 | OPEN_READONLY = OPEN_READONLY, 484 | OPEN_READWRITE = OPEN_READWRITE, 485 | OPEN_SHAREDCACHE = OPEN_SHAREDCACHE, 486 | OPEN_URI = OPEN_URI, 487 | OK = OK, 488 | sqlite = sqlite, 489 | Statement = Statement, 490 | finalize = finalize, 491 | isOpen = isOpen, 492 | bind = bind, 493 | bindOne = bindOne, 494 | bindMany = bindMany, 495 | query = query, 496 | query1 = query1, 497 | iquery = iquery, 498 | queryall = queryall, 499 | queryone = queryone, 500 | execute = execute, 501 | squery = squery, 502 | Database = Database, 503 | close = close, 504 | errorFor = errorFor, 505 | changesIn = changesIn, 506 | allChangesIn = allChangesIn, 507 | update = update, 508 | Transaction = Transaction 509 | } 510 | -------------------------------------------------------------------------------- /grasp/init.moon: -------------------------------------------------------------------------------- 1 | -- grasp 2 | -- Wrapper for the LuaSQLite3 API 3 | -- By daelvn 4 | import expect, typeset, typeof from require "grasp.util" 5 | sqlite = require "lsqlite3" 6 | unpack or= table.unpack 7 | 8 | -- Constants 9 | OPEN_READONLY = sqlite.OPEN_READONLY 10 | OPEN_READWRITE = sqlite.OPEN_READWRITE 11 | OPEN_CREATE = sqlite.OPEN_CREATE 12 | OPEN_URI = sqlite.OPEN_URI 13 | OPEN_MEMORY = sqlite.OPEN_MEMORY 14 | OPEN_NOMUTEX = sqlite.OPEN_NOMUTEX 15 | OPEN_FULLMUTEX = sqlite.OPEN_FULLMUTEX 16 | OPEN_SHAREDCACHE = sqlite.OPEN_SHAREDCACHE 17 | OPEN_PRIVATECACHE = sqlite.OPEN_PRIVATECACHE 18 | OK = sqlite.OK 19 | 20 | local changesIn 21 | 22 | -- Database :: (string, table) -> Database 23 | -- Database { 24 | -- db :: userdata 25 | -- attributes :: table 26 | -- filename :: string 27 | -- } 28 | -- 29 | -- Attributes 30 | -- readonly / ro :: boolean 31 | -- readwrite / rw :: boolean 32 | -- create :: boolean 33 | -- uri :: boolean 34 | -- memory :: boolean 35 | -- mutex :: boolean 36 | -- cache :: string [shared|private] 37 | -- volatile :: boolean 38 | Database = (filename, attr={create: true, rw: true}) -> 39 | expect 1, filename, {"string"} 40 | expect 2, attr, {"table"} 41 | -- check that we're not trying to open :memory: 42 | if filename == ":memory:" 43 | return typeset { 44 | :filename 45 | db: sqlite.open_memory! 46 | attributes: attr 47 | }, "Database" 48 | -- if we're trying to open a temporal one, do that 49 | if filename == "" 50 | filename = os.tmpname! 51 | attr.volatile = true 52 | -- get attributes 53 | flags = 0 54 | flags += OPEN_READONLY if attr.readonly or attr.ro 55 | flags += OPEN_READWRITE if attr.readwrite or attr.rw 56 | flags += OPEN_CREATE if attr.create 57 | flags += OPEN_URI if attr.uri 58 | flags += OPEN_MEMORY if attr.memory 59 | flags += OPEN_NOMUTEX if not attr.mutex 60 | flags += OPEN_FULLMUTEX if attr.mutex 61 | flags += OPEN_SHAREDCACHE if attr.cache == "shared" 62 | flags += OPEN_PRIVATECACHE if attr.cache == "private" 63 | -- open db 64 | db, code, msg = sqlite.open filename, flags 65 | return nil, code, msg unless db 66 | -- return object 67 | return typeset { 68 | :filename 69 | :db 70 | attributes: attr 71 | }, "Database" 72 | 73 | -- Statement :: Database -> string -> Statement 74 | -- Statement { 75 | -- sql :: string 76 | -- stat :: userdata 77 | -- } 78 | -- Creates a new prepared statement 79 | Statement ==> 80 | expect 1, @, {"Database"} 81 | return (sql) -> 82 | expect 2, sql, {"string"} 83 | -- complete with trailing semicolon if missing 84 | sql ..= ";" unless sql\match ";$" 85 | -- check that its valid 86 | error "Not a valid SQL statement: [[#{sql}]]" unless sqlite.complete sql 87 | -- prepare the statement 88 | stat = @db\prepare sql 89 | error "Could not prepare statement: [[#{sql}]], (#{stat})" if "userdata" != typeof stat 90 | return typeset { 91 | :sql, :stat, db: @ 92 | }, "Statement" 93 | 94 | -- Statement functions 95 | -- bind (bind_names) 96 | -- bindOne (bind+bind_parameter_name) 97 | -- bindMany (bind_values) 98 | -- finalize 99 | -- isOpen (isopen) 100 | -- query (nrows) 101 | -- query1 (rows) 102 | -- iquery (urows) 103 | -- queryall -- table of all rows in query 104 | -- queryone -- only the first row in query 105 | -- execute (step) 106 | 107 | -- finalize :: Statement -> (boolean, number) 108 | finalize ==> 109 | expect 1, @, {"Statement"} 110 | ok = @stat\finalize! 111 | return (ok == OK), ok 112 | 113 | -- isOpen :: Statement -> boolean 114 | isOpen ==> 115 | expect 1, @, {"Statement"} 116 | return @stat\isopen! 117 | 118 | -- bind :: Statement -> table -> (boolean, number) 119 | bind ==> 120 | expect 1, @, {"Statement"} 121 | return (nametable) -> 122 | expect 2, nametable, {"table"} 123 | print (require "inspect") @ unless @stat 124 | ok = @stat\bind_names nametable 125 | return (ok == OK), ok 126 | 127 | -- bindOne :: Statement -> (number|string, _) -> (boolean, number) 128 | bindOne ==> 129 | expect 1, @, {"Statement"} 130 | return (n, value) -> 131 | expect 2, n, {"number", "string"} 132 | ok = @stat\bind n, value 133 | return (ok == OK), ok 134 | 135 | -- bindMany :: Statement -> [_] 136 | bindMany ==> 137 | expect 1, @, {"Statement"} 138 | return (list) -> 139 | ok = @stat\bind_values unpack list 140 | return (ok == OK), ok 141 | 142 | -- query :: Statement -> (_ -> {string:_}) 143 | query ==> 144 | expect 1, @, {"Statement"} 145 | return @stat\nrows! 146 | 147 | -- query1 :: Statement -> (_ -> [_]) 148 | query1 ==> 149 | expect 1, @, {"Statement"} 150 | return @stat\rows! 151 | 152 | -- iquery :: Statement -> (_ -> ...) 153 | iquery ==> 154 | expect 1, @, {"Statement"} 155 | return @stat\urows! 156 | 157 | -- queryall :: Statement -> [table] 158 | queryall ==> 159 | expect 1, @, {"Statement"} 160 | r = [row for row in @stat\nrows!] 161 | @stat\reset! 162 | return r 163 | 164 | -- queryone :: Statement -> table 165 | queryone ==> 166 | expect 1, @, {"Statement"} 167 | r = (queryall @)[1] 168 | @stat\reset! 169 | return r 170 | 171 | -- squery :: Statement -> table|boolean 172 | squery ==> 173 | expect 1, @, {"Statement"} 174 | r = queryall @ 175 | @stat\reset! 176 | if #r > 0 177 | return r 178 | else 179 | changes = (changesIn @db) 180 | if changes > 0 181 | return affected_rows: changes 182 | else 183 | return true 184 | 185 | -- execute :: Statement -> (boolean, number) 186 | -- TODO return number of changes 187 | execute ==> 188 | expect 1, @, {"Statement"} 189 | status = @stat\step! 190 | @stat\reset! 191 | return (status == sqlite.DONE), status 192 | 193 | -- Database functions 194 | -- changesIn (changes) 195 | -- allChangesIn (total_changes) 196 | -- close 197 | -- errorFor (errcode+errmsg) 198 | -- update (exec) 199 | -- isOpen (isopen) 200 | -- query (nrows) 201 | -- query1 (rows) 202 | -- iquery (urows) 203 | -- queryall -- table of all rows in query 204 | -- queryone -- only the first row in query 205 | 206 | -- close :: Database -> (boolean, number) 207 | close ==> 208 | expect 1, @, {"Database"} 209 | ok = @db\close! 210 | -- if volatile, remove it 211 | if ok and @attributes.volatile and (filename != ":memory:") 212 | os.remove filename 213 | -- 214 | return (ok == OK), ok 215 | 216 | -- errorFor :: Database -> (number, string) 217 | errorFor ==> 218 | expect 1, @, {"Database"} 219 | return (@db\errcode!), (@db\errmsg!) 220 | 221 | -- changesIn :: Database -> number 222 | changesIn ==> 223 | expect 1, @, {"Database"} 224 | return @db\changes! 225 | 226 | -- allChangesIn :: Database -> number 227 | allChangesIn ==> 228 | expect 1, @, {"Database"} 229 | return @db\total_changes! 230 | 231 | -- isOpen :: Database -> boolean 232 | _Statement_isOpen = isOpen 233 | isOpen ==> 234 | return _Statement_isOpen @ if "Statement" == typeof @ 235 | expect 1, @, {"Database"} 236 | return @stat\isopen! 237 | 238 | -- update :: Database -> (string, table) -> (boolean, number) 239 | update ==> 240 | expect 1, @, {"Database"} 241 | return (sql, bindt={}) -> 242 | expect 2, sql, {"string"} 243 | expect 3, bindt, {"table"} 244 | stmt = (Statement @) sql 245 | unless (bind stmt) bindt 246 | error "update : Failed to bind to [[#{sql}]]" 247 | return execute stmt 248 | 249 | -- query :: Database -> (string, table) -> (_ -> {string:_}) 250 | _Statement_query = query 251 | query ==> 252 | return _Statement_query @ if "Statement" == typeof @ 253 | expect 1, @, {"Database"} 254 | return (sql, bindt={}) -> 255 | expect 2, sql, {"string"} 256 | expect 3, bindt, {"table"} 257 | stmt = (Statement @) sql 258 | unless (bind stmt) bindt 259 | error "query : Failed to bind to [[#{sql}]]" 260 | query stmt 261 | 262 | -- query1 :: Database -> (string, table) -> (_ -> [_]) 263 | _Statement_query1 = query1 264 | query1 ==> 265 | return _Statement_query1 @ if "Statement" == typeof @ 266 | expect 1, @, {"Database"} 267 | return (sql, bindt={}) -> 268 | expect 2, sql, {"string"} 269 | expect 3, bindt, {"table"} 270 | stmt = (Statement @) sql 271 | unless (bind stmt) bindt 272 | error "query1 : Failed to bind to [[#{sql}]]" 273 | query1 stmt 274 | 275 | -- iquery :: Database -> (string, table) -> (_ -> ...) 276 | _Statement_iquery = iquery 277 | iquery ==> 278 | return _Statement_iquery @ if "Statement" == typeof @ 279 | expect 1, @, {"Database"} 280 | return (sql, bindt={}) -> 281 | expect 2, sql, {"string"} 282 | expect 3, bindt, {"table"} 283 | stmt = (Statement @) sql 284 | unless (bind stmt) bindt 285 | error "iquery : Failed to bind to [[#{sql}]]" 286 | iquery stmt 287 | 288 | -- queryall :: Database -> (string, table) -> [table] 289 | _Statement_queryall = queryall 290 | queryall ==> 291 | return _Statement_queryall @ if "Statement" == typeof @ 292 | expect 1, @, {"Database"} 293 | return (sql, bindt={}) -> 294 | expect 2, sql, {"string"} 295 | expect 3, bindt, {"table"} 296 | stmt = (Statement @) sql 297 | unless (bind stmt) bindt 298 | error "queryall : Failed to bind to [[#{sql}]]" 299 | queryall stmt 300 | 301 | -- queryone :: Database -> (string, table) -> table 302 | _Statement_queryone = queryone 303 | queryone ==> 304 | return _Statement_queryone @ if "Statement" == typeof @ 305 | expect 1, @, {"Database"} 306 | return (sql, bindt={}) -> 307 | expect 2, sql, {"string"} 308 | expect 3, bindt, {"table"} 309 | stmt = (Statement @) sql 310 | unless (bind stmt) bindt 311 | error "queryone : Failed to bind to [[#{sql}]]" 312 | queryone stmt 313 | 314 | -- squery :: Database -> (string, table) -> table|boolean 315 | _Statement_squery = squery 316 | squery ==> 317 | return _Statement_squery @ if "Statement" == typeof @ 318 | expect 1, @, {"Database"} 319 | return (sql, bindt={}) -> 320 | expect 2, sql, {"string"} 321 | expect 3, bindt, {"table"} 322 | stmt = (Statement @) sql 323 | unless (bind stmt) bindt 324 | error "squery : Failed to bind to [[#{sql}]]" 325 | squery stmt 326 | 327 | -- Transaction :: Database -> (Database -> boolean) -> boolean 328 | Transaction ==> 329 | expect 1, @, {"Database"} 330 | upd = update @ 331 | (fn) -> 332 | expect 2, fn, {"function"} 333 | unless upd "SAVEPOINT grasp_savepoint" 334 | error "Could not start transaction (grasp_savepoint)" 335 | ok = pcall -> fn @ 336 | if ok 337 | upd "RELEASE grasp_savepoint" 338 | else 339 | upd "ROLLBACK TO grasp_savepoint" 340 | return ok 341 | 342 | { 343 | :OPEN_CREATE, :OPEN_FULLMUTEX, :OPEN_MEMORY, :OPEN_NOMUTEX 344 | :OPEN_PRIVATECACHE, :OPEN_READONLY, :OPEN_READWRITE, :OPEN_SHAREDCACHE, :OPEN_URI, 345 | :OK 346 | 347 | :sqlite 348 | 349 | :Statement 350 | :finalize, :isOpen, :bind, :bindOne, :bindMany 351 | :query, :query1, :iquery, :queryall, :queryone 352 | :execute, :squery 353 | 354 | :Database 355 | :close, :errorFor, :changesIn, :allChangesIn, :update 356 | 357 | :Transaction 358 | } -------------------------------------------------------------------------------- /grasp/query.lua: -------------------------------------------------------------------------------- 1 | local expect 2 | expect = require("grasp.util").expect 3 | local runwith 4 | runwith = function(fn, env, ...) 5 | setfenv(fn, env) 6 | return fn(...) 7 | end 8 | local emit 9 | emit = function(t) 10 | return function(add) 11 | t.str = t.str .. add 12 | end 13 | end 14 | local dquote 15 | dquote = function(txt) 16 | return ("%q"):format(txt) 17 | end 18 | local squote 19 | squote = function(txt) 20 | return "'" .. (tostring(txt)) .. "'" 21 | end 22 | local norm 23 | norm = function(v) 24 | if v == true then 25 | return 1 26 | end 27 | if v == false then 28 | return 0 29 | end 30 | if v == nil then 31 | return "NULL" 32 | end 33 | if "string" == type(v) then 34 | return squote(v) 35 | end 36 | if ("table" == type(v)) and v.date then 37 | return v.date 38 | end 39 | if ("table" == type(v)) and v.raw then 40 | return v.raw 41 | end 42 | return v 43 | end 44 | local env 45 | env = { 46 | reset = function() 47 | local resettable = { 48 | "temp", 49 | "always", 50 | "without_rowid", 51 | "replace", 52 | "rollback", 53 | "abort", 54 | "fail", 55 | "ignore", 56 | "name", 57 | "alias", 58 | "distinct", 59 | "all", 60 | "where", 61 | "order", 62 | "limit", 63 | "off" 64 | } 65 | for _index_0 = 1, #resettable do 66 | local rr = resettable[_index_0] 67 | env[rr] = false 68 | end 69 | end, 70 | sql = { 71 | date = function(str) 72 | return { 73 | date = "date('" .. tostring(str) .. "')" 74 | } 75 | end, 76 | raw = function(str) 77 | return { 78 | raw = str 79 | } 80 | end, 81 | queryplan = "QUERY PLAN", 82 | explain = function(attr, fn) 83 | if fn then 84 | if not (attr == "QUERY PLAN") then 85 | error("sql.explain: Attribute is not QUERY PLAN") 86 | end 87 | expect(2, fn, { 88 | "function" 89 | }) 90 | else 91 | fn = attr 92 | expect(1, fn, { 93 | "function" 94 | }) 95 | end 96 | local resl = { 97 | str = "" 98 | } 99 | local oldemit = env.emit 100 | env.emit = emit(resl) 101 | runwith(fn, env.sql) 102 | env.emit = oldemit 103 | if attr == "QUERY PLAN" then 104 | return env.emit("EXPLAIN QUERY PLAN " .. resl.str) 105 | else 106 | return env.emit("EXPLAIN " .. resl.str) 107 | end 108 | end, 109 | savepoint = function(name) 110 | expect(1, name, { 111 | "string" 112 | }) 113 | return env.emit("SAVEPOINT " .. tostring(name) .. ";") 114 | end, 115 | release = function(name) 116 | expect(1, name, { 117 | "string" 118 | }) 119 | return env.emit("RELEASE " .. tostring(name) .. ";") 120 | end, 121 | rollback = function(name) 122 | expect(1, name, { 123 | "string", 124 | "nil" 125 | }) 126 | if name then 127 | return env.emit("ROLLBACK TO " .. tostring(name) .. ";") 128 | else 129 | return env.emit("ROLLBACK TRANSACTION;") 130 | end 131 | end, 132 | deferred = "DEFERRED", 133 | immediate = "IMMEDIATE", 134 | exclusive = "EXCLUSIVE", 135 | begin = function(attr) 136 | if attr == nil then 137 | attr = "" 138 | end 139 | expect(1, attr, { 140 | "string", 141 | "nil" 142 | }) 143 | return env.emit("BEGIN " .. tostring(attr) .. " TRANSACTION") 144 | end, 145 | commit = function() 146 | return env.emit("COMMIT TRANSACTION;") 147 | end, 148 | End = function() 149 | return env.emit("END TRANSACTION;") 150 | end, 151 | create = function(name, fn) 152 | expect(1, name, { 153 | "string" 154 | }) 155 | expect(2, fn, { 156 | "function" 157 | }) 158 | local retv = runwith(fn, env.create) 159 | local keys 160 | do 161 | local _accum_0 = { } 162 | local _len_0 = 1 163 | for k, _ in pairs(retv.columns) do 164 | _accum_0[_len_0] = k 165 | _len_0 = _len_0 + 1 166 | end 167 | keys = _accum_0 168 | end 169 | local this = "CREATE" 170 | if env.temp then 171 | this = this .. " TEMPORARY" 172 | end 173 | this = this .. " TABLE" 174 | if not (env.always) then 175 | this = this .. " IF NOT EXISTS" 176 | end 177 | this = this .. " " .. tostring(dquote(name)) 178 | this = this .. "(\n" 179 | local _max_0 = #keys - 1 180 | for _index_0 = 1, _max_0 < 0 and #keys + _max_0 or _max_0 do 181 | local k = keys[_index_0] 182 | this = this .. " " .. tostring(dquote(k)) .. " " .. tostring(norm(retv.columns[k])) .. ",\n" 183 | end 184 | this = this .. " " .. tostring(dquote(keys[#keys])) .. " " .. tostring(norm(retv.columns[keys[#keys]])) 185 | this = this .. ")" 186 | if env.without_rowid then 187 | this = this .. " WITHOUT ROWID" 188 | end 189 | this = this .. ";" 190 | env.emit(this) 191 | return env.reset() 192 | end, 193 | insert = function(fn, into, replace) 194 | if replace == nil then 195 | replace = false 196 | end 197 | expect(1, fn, { 198 | "function" 199 | }) 200 | expect(2, into, { 201 | "string", 202 | "nil" 203 | }) 204 | expect(3, replace, { 205 | "boolean" 206 | }) 207 | local retv = runwith(fn, env.insert) 208 | local values = retv.values 209 | local keys 210 | do 211 | local _accum_0 = { } 212 | local _len_0 = 1 213 | for k, _ in pairs(values) do 214 | _accum_0[_len_0] = k 215 | _len_0 = _len_0 + 1 216 | end 217 | keys = _accum_0 218 | end 219 | env.name = env.name or into 220 | local this = replace and "REPLACE" or "INSERT" 221 | if env.replace then 222 | this = this .. " OR REPLACE" 223 | end 224 | if env.rollback then 225 | this = this .. " OR ROLLBACK" 226 | end 227 | if env.abort then 228 | this = this .. " OR ABORT" 229 | end 230 | if env.fail then 231 | this = this .. " OR FAIL" 232 | end 233 | if env.ignore then 234 | this = this .. " OR IGNORE" 235 | end 236 | if env.name or into then 237 | this = this .. " INTO " .. tostring(dquote(env.name)) 238 | else 239 | error("sql.insert: Expected 'into '") 240 | end 241 | if env.alias then 242 | this = this .. " AS " .. tostring(dquote(env.alias)) 243 | end 244 | this = this .. "(" 245 | local _max_0 = #keys - 1 246 | for _index_0 = 1, _max_0 < 0 and #keys + _max_0 or _max_0 do 247 | local k = keys[_index_0] 248 | this = this .. tostring(dquote(k)) .. ", " 249 | end 250 | this = this .. tostring(dquote(keys[#keys])) 251 | this = this .. ")" 252 | this = this .. " VALUES (\n" 253 | local _max_1 = #keys - 1 254 | for _index_0 = 1, _max_1 < 0 and #keys + _max_1 or _max_1 do 255 | local k = keys[_index_0] 256 | this = this .. " " .. tostring(norm(values[k])) .. ",\n" 257 | end 258 | this = this .. " " .. tostring(norm(values[keys[#keys]])) .. "\n" 259 | this = this .. ");" 260 | env.emit(this) 261 | return env.reset() 262 | end, 263 | replace = function(fn, into) 264 | return env.sql.insert(fn, into, true) 265 | end, 266 | into = function(a, b) 267 | return b, a 268 | end, 269 | select = function(res, fn, fr) 270 | expect(1, res, { 271 | "string" 272 | }) 273 | expect(2, fn, { 274 | "function" 275 | }) 276 | expect(3, fr, { 277 | "string", 278 | "nil" 279 | }) 280 | runwith(fn, env.select) 281 | env.name = env.name or fr 282 | local this = "SELECT" 283 | if env.distinct then 284 | this = this .. " DISTINCT" 285 | end 286 | if env.all then 287 | this = this .. " ALL" 288 | end 289 | this = this .. " " .. tostring(res) 290 | if env.name then 291 | this = this .. " FROM " .. tostring(dquote(env.name)) 292 | else 293 | error("sql.select: Expected 'From '") 294 | end 295 | if env.where then 296 | this = this .. " WHERE " .. tostring(env.where) 297 | end 298 | if env.ord then 299 | this = this .. " ORDER BY " .. tostring(env.ord) 300 | end 301 | if env.limit then 302 | this = this .. " LIMIT " .. tostring(env.limit) 303 | end 304 | if env.off then 305 | this = this .. " OFFSET " .. tostring(env.off) 306 | end 307 | this = this .. ";" 308 | env.emit(this) 309 | return env.reset() 310 | end, 311 | From = function(a, b) 312 | return b, a 313 | end, 314 | delete = function(fn, fr) 315 | expect(1, fn, { 316 | "function" 317 | }) 318 | expect(2, fr, { 319 | "string", 320 | "nil" 321 | }) 322 | runwith(fn, env.delete) 323 | env.name = env.name or fr 324 | local this 325 | if env.name then 326 | this = "DELETE FROM " .. tostring(dquote(env.name)) 327 | else 328 | error("sql.delete: Expected 'From '") 329 | end 330 | if env.where then 331 | this = this .. " WHERE " .. tostring(env.where) 332 | end 333 | this = this .. ";" 334 | env.emit(this) 335 | return env.reset() 336 | end, 337 | drop = function(name, fn) 338 | expect(1, name, { 339 | "string" 340 | }) 341 | expect(2, fn, { 342 | "function", 343 | "nil" 344 | }) 345 | if fn then 346 | runwith(fn, env.drop) 347 | end 348 | local this = "DROP TABLE" 349 | if not (env.always) then 350 | this = this .. " IF EXISTS" 351 | end 352 | this = this .. " " .. tostring(dquote(name)) .. ";" 353 | env.emit(this) 354 | return env.reset() 355 | end, 356 | update = function(name, fn) 357 | expect(1, name, { 358 | "string" 359 | }) 360 | expect(2, fn, { 361 | "function" 362 | }) 363 | local retv = runwith(fn, env.update) 364 | local values = retv.values 365 | local keys 366 | do 367 | local _accum_0 = { } 368 | local _len_0 = 1 369 | for k, _ in pairs(values) do 370 | _accum_0[_len_0] = k 371 | _len_0 = _len_0 + 1 372 | end 373 | keys = _accum_0 374 | end 375 | local this = "UPDATE" 376 | if env.replace then 377 | this = this .. " OR REPLACE" 378 | end 379 | if env.rollback then 380 | this = this .. " OR ROLLBACK" 381 | end 382 | if env.abort then 383 | this = this .. " OR ABORT" 384 | end 385 | if env.fail then 386 | this = this .. " OR FAIL" 387 | end 388 | if env.ignore then 389 | this = this .. " OR IGNORE" 390 | end 391 | this = this .. " " .. tostring(dquote(name)) 392 | this = this .. " SET" 393 | local _max_0 = #keys - 1 394 | for _index_0 = 1, _max_0 < 0 and #keys + _max_0 or _max_0 do 395 | local k = keys[_index_0] 396 | this = this .. " " .. tostring(dquote(k)) .. " = " .. tostring(norm(values[k])) .. "," 397 | end 398 | this = this .. " " .. tostring(dquote(keys[#keys])) .. " = " .. tostring(norm(values[keys[#keys]])) 399 | if env.where then 400 | this = this .. " WHERE " .. tostring(env.where) 401 | end 402 | this = this .. ";" 403 | env.emit(this) 404 | return env.reset() 405 | end 406 | }, 407 | create = { 408 | temporary = function() 409 | env.temp = true 410 | end, 411 | always = function() 412 | env.always = true 413 | end, 414 | without_rowid = function() 415 | env.without_rowid = true 416 | end, 417 | date = function(str) 418 | return { 419 | date = "date('" .. tostring(str) .. "')" 420 | } 421 | end, 422 | raw = function(str) 423 | return { 424 | raw = str 425 | } 426 | end 427 | }, 428 | insert = { 429 | replace = function() 430 | env.replace = true 431 | end, 432 | rollback = function() 433 | env.rollback = true 434 | end, 435 | abort = function() 436 | env.abort = true 437 | end, 438 | fail = function() 439 | env.fail = true 440 | end, 441 | ignore = function() 442 | env.ignore = true 443 | end, 444 | into = function(name) 445 | env.name = norm(name) 446 | end, 447 | alias = function(name) 448 | env.alias = name 449 | end, 450 | date = function(str) 451 | return { 452 | date = "date('" .. tostring(str) .. "')" 453 | } 454 | end, 455 | raw = function(str) 456 | return { 457 | raw = str 458 | } 459 | end 460 | }, 461 | update = { 462 | replace = function() 463 | env.replace = true 464 | end, 465 | rollback = function() 466 | env.rollback = true 467 | end, 468 | abort = function() 469 | env.abort = true 470 | end, 471 | fail = function() 472 | env.fail = true 473 | end, 474 | ignore = function() 475 | env.ignore = true 476 | end, 477 | date = function(str) 478 | return { 479 | date = "date('" .. tostring(str) .. "')" 480 | } 481 | end, 482 | raw = function(str) 483 | return { 484 | raw = str 485 | } 486 | end, 487 | where = function(any) 488 | local oldwhere = env.where 489 | if "table" == type(any) then 490 | local this = "" 491 | for k, v in pairs(any) do 492 | this = this .. tostring(dquote(k)) .. " = " .. tostring(norm(v)) .. " AND" 493 | end 494 | env.where = this:match("(.+) AND") 495 | else 496 | env.where = any 497 | end 498 | if oldwhere then 499 | env.where = tostring(oldwhere) .. " AND " .. tostring(env.where) 500 | end 501 | end 502 | }, 503 | select = { 504 | distinct = function() 505 | env.distinct = true 506 | end, 507 | all = function() 508 | env.all = true 509 | end, 510 | date = function(str) 511 | return { 512 | date = "date('" .. tostring(str) .. "')" 513 | } 514 | end, 515 | raw = function(str) 516 | return { 517 | raw = str 518 | } 519 | end, 520 | From = function(name) 521 | env.name = name 522 | end, 523 | order = function(ord) 524 | env.order = ord 525 | end, 526 | limit = function(lim) 527 | env.limit = lim 528 | end, 529 | offset = function(off) 530 | env.off = off 531 | end, 532 | where = function(any) 533 | local oldwhere = env.where 534 | if "table" == type(any) then 535 | local this = "" 536 | for k, v in pairs(any) do 537 | this = this .. tostring(dquote(k)) .. " = " .. tostring(norm(v)) .. " AND" 538 | end 539 | env.where = this:match("(.+) AND") 540 | else 541 | env.where = any 542 | end 543 | if oldwhere then 544 | env.where = tostring(oldwhere) .. " AND " .. tostring(env.where) 545 | end 546 | end 547 | }, 548 | delete = { 549 | date = function(str) 550 | return { 551 | date = "date('" .. tostring(str) .. "')" 552 | } 553 | end, 554 | raw = function(str) 555 | return { 556 | raw = str 557 | } 558 | end, 559 | From = function(name) 560 | env.name = name 561 | end, 562 | where = function(any) 563 | local oldwhere = env.where 564 | if "table" == type(any) then 565 | local this = "" 566 | for k, v in pairs(any) do 567 | this = this .. tostring(dquote(k)) .. " = " .. tostring(norm(v)) .. " AND" 568 | end 569 | env.where = this:match("(.+) AND") 570 | else 571 | env.where = any 572 | end 573 | if oldwhere then 574 | env.where = tostring(oldwhere) .. " AND " .. tostring(env.where) 575 | end 576 | end 577 | }, 578 | drop = { 579 | ifexists = function() 580 | env.always = true 581 | end 582 | } 583 | } 584 | local sql 585 | sql = function(fn) 586 | local result = { 587 | str = "" 588 | } 589 | env.emit = emit(result) 590 | runwith(fn, env.sql) 591 | return result.str 592 | end 593 | return { 594 | sql = sql, 595 | norm = norm 596 | } 597 | -------------------------------------------------------------------------------- /grasp/query.moon: -------------------------------------------------------------------------------- 1 | -- grasp.query 2 | -- SQLite Query Builder 3 | -- By daelvn 4 | import expect from require "grasp.util" 5 | 6 | -- Run with environment 7 | runwith = (fn, env, ...) -> 8 | setfenv fn, env 9 | return fn ... 10 | 11 | -- emitting function 12 | emit = (t) -> (add) -> t.str ..= add 13 | 14 | -- quoting 15 | dquote = (txt) -> ("%q")\format txt 16 | squote = (txt) -> "'" .. (tostring txt) .. "'" 17 | 18 | -- normalize a value 19 | norm = (v) -> 20 | return 1 if v == true 21 | return 0 if v == false 22 | return "NULL" if v == nil 23 | return squote v if "string" == type v 24 | return v.date if ("table" == type v) and v.date 25 | return v.raw if ("table" == type v) and v.raw 26 | return v 27 | 28 | -- sql environments 29 | local env 30 | env = { 31 | -- reset! 32 | reset: -> 33 | resettable = { 34 | "temp", "always", "without_rowid", "replace", "rollback" 35 | "abort", "fail", "ignore", "name", "alias", "distinct", "all" 36 | "where", "order", "limit", "off" 37 | } 38 | env[rr] = false for rr in *resettable 39 | -- sql 40 | sql: { 41 | -- date & raw 42 | date: (str) -> date: "date('#{str}')" 43 | raw: (str) -> raw: str 44 | -- query plan 45 | queryplan: "QUERY PLAN" 46 | -- explain 47 | explain: (attr, fn) -> 48 | if fn 49 | error "sql.explain: Attribute is not QUERY PLAN" unless attr == "QUERY PLAN" 50 | expect 2, fn, {"function"} 51 | else 52 | fn = attr 53 | expect 1, fn, {"function"} 54 | -- 55 | resl = str: "" 56 | oldemit = env.emit 57 | env.emit = emit resl 58 | runwith fn, env.sql 59 | env.emit = oldemit 60 | -- 61 | if attr == "QUERY PLAN" 62 | env.emit "EXPLAIN QUERY PLAN " .. resl.str 63 | else 64 | env.emit "EXPLAIN " .. resl.str 65 | -- savepoint 66 | savepoint: (name) -> 67 | expect 1, name, {"string"} 68 | env.emit "SAVEPOINT #{name};" 69 | -- release 70 | release: (name) -> 71 | expect 1, name, {"string"} 72 | env.emit "RELEASE #{name};" 73 | -- rollback 74 | rollback: (name) -> 75 | expect 1, name, {"string", "nil"} 76 | if name 77 | env.emit "ROLLBACK TO #{name};" 78 | else 79 | env.emit "ROLLBACK TRANSACTION;" 80 | -- begin 81 | deferred: "DEFERRED" 82 | immediate: "IMMEDIATE" 83 | exclusive: "EXCLUSIVE" 84 | begin: (attr="") -> 85 | expect 1, attr, {"string", "nil"} 86 | env.emit "BEGIN #{attr} TRANSACTION" 87 | -- commit/end 88 | commit: -> env.emit "COMMIT TRANSACTION;" 89 | End: -> env.emit "END TRANSACTION;" 90 | -- create 91 | create: (name, fn) -> 92 | expect 1, name, {"string"} 93 | expect 2, fn, {"function"} 94 | retv = runwith fn, env.create 95 | keys = [k for k, _ in pairs retv.columns] 96 | -- 97 | this = "CREATE" 98 | this ..= " TEMPORARY" if env.temp 99 | this ..= " TABLE" 100 | this ..= " IF NOT EXISTS" unless env.always 101 | this ..= " #{dquote name}" 102 | this ..= "(\n" 103 | this ..= " #{dquote k} #{norm retv.columns[k]},\n" for k in *keys[,#keys-1] 104 | this ..= " #{dquote keys[#keys]} #{norm retv.columns[keys[#keys]]}" 105 | this ..= ")" 106 | this ..= " WITHOUT ROWID" if env.without_rowid 107 | this ..= ";" 108 | -- 109 | env.emit this 110 | env.reset! 111 | -- insert 112 | insert: (fn, into, replace=false) -> 113 | expect 1, fn, {"function"} 114 | expect 2, into, {"string", "nil"} 115 | expect 3, replace, {"boolean"} 116 | retv = runwith fn, env.insert 117 | values = retv.values 118 | keys = [k for k, _ in pairs values] 119 | env.name or= into 120 | -- 121 | this = replace and "REPLACE" or "INSERT" 122 | this ..= " OR REPLACE" if env.replace 123 | this ..= " OR ROLLBACK" if env.rollback 124 | this ..= " OR ABORT" if env.abort 125 | this ..= " OR FAIL" if env.fail 126 | this ..= " OR IGNORE" if env.ignore 127 | this ..= " INTO #{dquote env.name}" if env.name or into else error "sql.insert: Expected 'into '" 128 | this ..= " AS #{dquote env.alias}" if env.alias 129 | this ..= "(" 130 | this ..= "#{dquote k}, " for k in *keys[,#keys-1] 131 | this ..= "#{dquote keys[#keys]}" 132 | this ..= ")" 133 | this ..= " VALUES (\n" 134 | this ..= " #{norm values[k]},\n" for k in *keys[,#keys-1] 135 | this ..= " #{norm values[keys[#keys]]}\n" 136 | this ..= ");" 137 | -- 138 | env.emit this 139 | env.reset! 140 | -- replace 141 | replace: (fn, into) -> env.sql.insert fn, into, true 142 | -- into hack 143 | into: (a, b) -> b, a 144 | -- select 145 | select: (res, fn, fr) -> 146 | expect 1, res, {"string"} 147 | expect 2, fn, {"function"} 148 | expect 3, fr, {"string", "nil"} 149 | runwith fn, env.select 150 | env.name or= fr 151 | -- 152 | this = "SELECT" 153 | this ..= " DISTINCT" if env.distinct 154 | this ..= " ALL" if env.all 155 | this ..= " #{res}" 156 | this ..= " FROM #{dquote env.name}" if env.name else error "sql.select: Expected 'From '" 157 | this ..= " WHERE #{env.where}" if env.where 158 | this ..= " ORDER BY #{env.ord}" if env.ord 159 | this ..= " LIMIT #{env.limit}" if env.limit 160 | this ..= " OFFSET #{env.off}" if env.off 161 | this ..= ";" 162 | -- 163 | env.emit this 164 | env.reset! 165 | -- from hack 166 | From: (a, b) -> b, a 167 | -- delete 168 | delete: (fn, fr) -> 169 | expect 1, fn, {"function"} 170 | expect 2, fr, {"string", "nil"} 171 | runwith fn, env.delete 172 | env.name or= fr 173 | -- 174 | this = "DELETE FROM #{dquote env.name}" if env.name else error "sql.delete: Expected 'From '" 175 | this ..= " WHERE #{env.where}" if env.where 176 | this ..= ";" 177 | -- 178 | env.emit this 179 | env.reset! 180 | -- drop table 181 | drop: (name, fn) -> 182 | expect 1, name, {"string"} 183 | expect 2, fn, {"function", "nil"} 184 | runwith fn, env.drop if fn 185 | -- 186 | this = "DROP TABLE" 187 | this ..= " IF EXISTS" unless env.always 188 | this ..= " #{dquote name};" 189 | -- 190 | env.emit this 191 | env.reset! 192 | -- update table 193 | update: (name, fn) -> 194 | expect 1, name, {"string"} 195 | expect 2, fn, {"function"} 196 | retv = runwith fn, env.update 197 | values = retv.values 198 | keys = [k for k, _ in pairs values] 199 | -- 200 | this = "UPDATE" 201 | this ..= " OR REPLACE" if env.replace 202 | this ..= " OR ROLLBACK" if env.rollback 203 | this ..= " OR ABORT" if env.abort 204 | this ..= " OR FAIL" if env.fail 205 | this ..= " OR IGNORE" if env.ignore 206 | this ..= " #{dquote name}" 207 | this ..= " SET" 208 | this ..= " #{dquote k} = #{norm values[k]}," for k in *keys[,#keys-1] 209 | this ..= " #{dquote keys[#keys]} = #{norm values[keys[#keys]]}" 210 | this ..= " WHERE #{env.where}" if env.where 211 | this ..= ";" 212 | -- 213 | env.emit this 214 | env.reset! 215 | } 216 | -- create 217 | create: { 218 | temporary: -> env.temp = true 219 | always: -> env.always = true 220 | without_rowid: -> env.without_rowid = true 221 | date: (str) -> date: "date('#{str}')" 222 | raw: (str) -> raw: str 223 | } 224 | -- insert 225 | insert: { 226 | replace: -> env.replace = true 227 | rollback: -> env.rollback = true 228 | abort: -> env.abort = true 229 | fail: -> env.fail = true 230 | ignore: -> env.ignore = true 231 | -- 232 | into: (name) -> env.name = norm name 233 | alias: (name) -> env.alias = name 234 | date: (str) -> date: "date('#{str}')" 235 | raw: (str) -> raw: str 236 | } 237 | -- update 238 | update: { 239 | replace: -> env.replace = true 240 | rollback: -> env.rollback = true 241 | abort: -> env.abort = true 242 | fail: -> env.fail = true 243 | ignore: -> env.ignore = true 244 | 245 | -- 246 | date: (str) -> date: "date('#{str}')" 247 | raw: (str) -> raw: str 248 | where: (any) -> 249 | oldwhere = env.where 250 | if "table" == type any 251 | this = "" 252 | for k, v in pairs any 253 | this ..= "#{dquote k} = #{norm v} AND" 254 | env.where = this\match "(.+) AND" 255 | else env.where = any 256 | env.where = "#{oldwhere} AND #{env.where}" if oldwhere 257 | } 258 | -- select 259 | select: { 260 | distinct: -> env.distinct = true 261 | all: -> env.all = true 262 | -- 263 | date: (str) -> date: "date('#{str}')" 264 | raw: (str) -> raw: str 265 | From: (name) -> env.name = name 266 | order: (ord) -> env.order = ord 267 | limit: (lim) -> env.limit = lim 268 | offset: (off) -> env.off = off 269 | where: (any) -> 270 | oldwhere = env.where 271 | if "table" == type any 272 | this = "" 273 | for k, v in pairs any 274 | this ..= "#{dquote k} = #{norm v} AND" 275 | env.where = this\match "(.+) AND" 276 | else env.where = any 277 | env.where = "#{oldwhere} AND #{env.where}" if oldwhere 278 | } 279 | -- delete 280 | delete: { 281 | date: (str) -> date: "date('#{str}')" 282 | raw: (str) -> raw: str 283 | From: (name) -> env.name = name 284 | where: (any) -> 285 | oldwhere = env.where 286 | if "table" == type any 287 | this = "" 288 | for k, v in pairs any 289 | this ..= "#{dquote k} = #{norm v} AND" 290 | env.where = this\match "(.+) AND" 291 | else env.where = any 292 | env.where = "#{oldwhere} AND #{env.where}" if oldwhere 293 | } 294 | -- drop 295 | drop: { 296 | ifexists: -> env.always = true 297 | } 298 | } 299 | 300 | -- Main 301 | sql = (fn) -> 302 | result = str: "" 303 | env.emit = emit result 304 | runwith fn, env.sql 305 | return result.str 306 | 307 | { 308 | :sql, :norm 309 | } -------------------------------------------------------------------------------- /grasp/util.lua: -------------------------------------------------------------------------------- 1 | local typeof 2 | typeof = function(v) 3 | local meta 4 | if "table" == type(v) then 5 | do 6 | local type_mt = getmetatable(v) 7 | if type_mt then 8 | meta = type_mt.__type 9 | end 10 | end 11 | end 12 | if meta then 13 | local _exp_0 = type(meta) 14 | if "function" == _exp_0 then 15 | return meta(v) 16 | elseif "string" == _exp_0 then 17 | return meta 18 | end 19 | elseif io.type(v) then 20 | return "io" 21 | else 22 | return type(v) 23 | end 24 | end 25 | local expect 26 | expect = function(n, v, ts) 27 | for _index_0 = 1, #ts do 28 | local ty = ts[_index_0] 29 | if ty == typeof(v) then 30 | return true 31 | end 32 | end 33 | return error("bad argument #" .. tostring(n) .. " (expected " .. tostring(table.concat(ts, ' or ')) .. ", got " .. tostring(type(v)) .. ")", 2) 34 | end 35 | local typeset 36 | typeset = function(v, ty) 37 | expect(1, v, { 38 | "table" 39 | }) 40 | do 41 | local mt = getmetatable(v) 42 | if mt then 43 | mt.__type = ty 44 | else 45 | setmetatable(v, { 46 | __type = ty 47 | }) 48 | end 49 | end 50 | return v 51 | end 52 | return { 53 | expect = expect, 54 | typeof = typeof, 55 | typeset = typeset 56 | } 57 | -------------------------------------------------------------------------------- /grasp/util.moon: -------------------------------------------------------------------------------- 1 | -- grasp.util 2 | -- Util functions for grasp 3 | -- By daelvn 4 | 5 | typeof = (v) -> 6 | -- get metatable 7 | local meta 8 | if "table" == type v 9 | if type_mt = getmetatable v 10 | meta = type_mt.__type 11 | -- check how to obtain type 12 | -- __type 13 | if meta 14 | switch type meta 15 | when "function" then return meta v 16 | when "string" then return meta 17 | -- io.type() 18 | elseif io.type v 19 | return "io" 20 | -- type() 21 | else 22 | return type v 23 | 24 | expect = (n, v, ts) -> 25 | for ty in *ts 26 | return true if ty == typeof v 27 | error "bad argument ##{n} (expected #{table.concat ts, ' or '}, got #{type v})", 2 28 | 29 | typeset = (v, ty) -> 30 | expect 1, v, {"table"} 31 | if mt = getmetatable v 32 | mt.__type = ty 33 | else 34 | setmetatable v, __type: ty 35 | return v 36 | 37 | { 38 | :expect, :typeof, :typeset 39 | } -------------------------------------------------------------------------------- /rock.yml: -------------------------------------------------------------------------------- 1 | package: grasp 2 | source: 3 | url: git://github.com/daelvn/grasp 4 | description: 5 | summary: A wrapper around lsqlite3 6 | detailed: > 7 | Grasp is a wrapper around the LuaSQLite3 8 | binding for sqlite3. Designed to be 9 | fairly similar to akojo/clutch, 10 | while having a more functional approach. 11 | homepage: https://github.com/daelvn/grasp 12 | dependencies: [ lsqlite3 ] 13 | build: 14 | type: "builtin" 15 | modules: 16 | grasp.init: grasp/init.lua 17 | grasp.util: grasp/util.lua 18 | grasp.query: grasp/query.lua -------------------------------------------------------------------------------- /rockspecs/grasp-1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "grasp" 2 | version = "1.1-1" 3 | source = { 4 | url = "git://github.com/daelvn/grasp", 5 | tag = "v1.1" 6 | } 7 | description = { 8 | summary = "A wrapper around lsqlite3", 9 | detailed = [[ 10 | Grasp is a wrapper around the LuaSQLite3 11 | binding for sqlite3. Designed to be 12 | fairly similar to akojo/clutch, 13 | while having a more functional approach. 14 | ]], 15 | homepage = "https://github.com/daelvn/grasp" 16 | } 17 | dependencies = { 18 | "lsqlite3", 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | ["grasp.init"] = "grasp/init.lua", 24 | ["grasp.util"] = "grasp/util.lua", 25 | ["grasp.query"] = "grasp/query.lua", 26 | } 27 | } -------------------------------------------------------------------------------- /rockspecs/grasp-1.2-1.rockspec: -------------------------------------------------------------------------------- 1 | build = { 2 | modules = { 3 | ["grasp.init"] = "grasp/init.lua", 4 | ["grasp.query"] = "grasp/query.lua", 5 | ["grasp.util"] = "grasp/util.lua" 6 | }, 7 | type = "builtin" 8 | } 9 | dependencies = { 10 | "lsqlite3" 11 | } 12 | description = { 13 | detailed = "Grasp is a wrapper around the LuaSQLite3 binding for sqlite3. Designed to be fairly similar to akojo/clutch, while having a more functional approach.\n", 14 | homepage = "https://github.com/daelvn/grasp", 15 | summary = "A wrapper around lsqlite3" 16 | } 17 | package = "grasp" 18 | rockspec_format = "3.0" 19 | source = { 20 | tag = "v1.2", 21 | url = "git://github.com/daelvn/grasp" 22 | } 23 | version = "1.2-1" 24 | -------------------------------------------------------------------------------- /rockspecs/grasp-1.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | build = { 2 | modules = { 3 | ["grasp.init"] = "grasp/init.lua", 4 | ["grasp.query"] = "grasp/query.lua", 5 | ["grasp.util"] = "grasp/util.lua" 6 | }, 7 | type = "builtin" 8 | } 9 | dependencies = { 10 | "lsqlite3" 11 | } 12 | description = { 13 | detailed = "Grasp is a wrapper around the LuaSQLite3 binding for sqlite3. Designed to be fairly similar to akojo/clutch, while having a more functional approach.\n", 14 | homepage = "https://github.com/daelvn/grasp", 15 | summary = "A wrapper around lsqlite3" 16 | } 17 | package = "grasp" 18 | rockspec_format = "3.0" 19 | source = { 20 | tag = "v1.2.1", 21 | url = "git://github.com/daelvn/grasp" 22 | } 23 | version = "1.2.1-1" 24 | -------------------------------------------------------------------------------- /rockspecs/grasp-1.2.2-1.rockspec: -------------------------------------------------------------------------------- 1 | build = { 2 | modules = { 3 | ["grasp.init"] = "grasp/init.lua", 4 | ["grasp.query"] = "grasp/query.lua", 5 | ["grasp.util"] = "grasp/util.lua" 6 | }, 7 | type = "builtin" 8 | } 9 | dependencies = { 10 | "lsqlite3" 11 | } 12 | description = { 13 | detailed = "Grasp is a wrapper around the LuaSQLite3 binding for sqlite3. Designed to be fairly similar to akojo/clutch, while having a more functional approach.\n", 14 | homepage = "https://github.com/daelvn/grasp", 15 | summary = "A wrapper around lsqlite3" 16 | } 17 | package = "grasp" 18 | rockspec_format = "3.0" 19 | source = { 20 | tag = "v1.2.2", 21 | url = "git://github.com/daelvn/grasp" 22 | } 23 | version = "1.2.2-1" 24 | -------------------------------------------------------------------------------- /rockspecs/grasp-1.3-1.rockspec: -------------------------------------------------------------------------------- 1 | build = { 2 | modules = { 3 | ["grasp.init"] = "grasp/init.lua", 4 | ["grasp.query"] = "grasp/query.lua", 5 | ["grasp.util"] = "grasp/util.lua" 6 | }, 7 | type = "builtin" 8 | } 9 | dependencies = { 10 | "lsqlite3" 11 | } 12 | description = { 13 | detailed = "Grasp is a wrapper around the LuaSQLite3 binding for sqlite3. Designed to be fairly similar to akojo/clutch, while having a more functional approach.\n", 14 | homepage = "https://github.com/daelvn/grasp", 15 | summary = "A wrapper around lsqlite3" 16 | } 17 | package = "grasp" 18 | rockspec_format = "3.0" 19 | source = { 20 | tag = "v1.3", 21 | url = "git://github.com/daelvn/grasp" 22 | } 23 | version = "1.3-1" 24 | -------------------------------------------------------------------------------- /spec/grasp_spec.moon: -------------------------------------------------------------------------------- 1 | grasp = require "grasp" 2 | import typeof from require "grasp.util" 3 | 4 | describe "grasp", -> 5 | import query, squery from grasp 6 | local db 7 | 8 | setup -> 9 | import Database, update from grasp 10 | db = Database ":memory:" 11 | 12 | statements = { 13 | [[CREATE TABLE S 14 | ( 15 | SNUM int NOT NULL PRIMARY KEY, 16 | SNAME varchar(16) NOT NULL UNIQUE, 17 | STATUS int NOT NULL, 18 | CITY varchar(20) NOT NULL 19 | );]] 20 | [[CREATE TABLE P 21 | ( 22 | PNUM int NOT NULL PRIMARY KEY, 23 | PNAME varchar(18) NOT NULL, 24 | COLOR varchar(10) NOT NULL, 25 | WEIGHT decimal(4,1) NOT NULL, 26 | CITY varchar(20) NOT NULL, 27 | UNIQUE (PNAME, COLOR, CITY) 28 | );]] 29 | [[CREATE TABLE SP 30 | ( 31 | SNUM int NOT NULL REFERENCES S, 32 | PNUM int NOT NULL REFERENCES P, 33 | QTY int NOT NULL, 34 | PRIMARY KEY (SNUM, PNUM) 35 | );]] 36 | [[INSERT INTO S VALUES (1, 'Smith', 20, 'London');]] 37 | [[INSERT INTO S VALUES (2, 'Jones', 10, 'Paris');]] 38 | [[INSERT INTO S VALUES (3, 'Blake', 30, 'Paris');]] 39 | [[INSERT INTO S VALUES (4, 'Clark', 20, 'London');]] 40 | [[INSERT INTO S VALUES (5, 'Adams', 30, 'Athens');]] 41 | [[INSERT INTO P VALUES (1, 'Nut', 'Red', 12, 'London');]] 42 | [[INSERT INTO P VALUES (2, 'Bolt', 'Green', 17, 'Paris');]] 43 | [[INSERT INTO P VALUES (3, 'Screw', 'Blue', 17, 'Oslo');]] 44 | [[INSERT INTO P VALUES (4, 'Screw', 'Red', 14, 'London');]] 45 | [[INSERT INTO P VALUES (5, 'Cam', 'Blue', 12, 'Paris');]] 46 | [[INSERT INTO P VALUES (6, 'Cog', 'Red', 19, 'London');]] 47 | [[INSERT INTO SP VALUES (1, 1, 300);]] 48 | [[INSERT INTO SP VALUES (1, 2, 200);]] 49 | [[INSERT INTO SP VALUES (1, 3, 400);]] 50 | [[INSERT INTO SP VALUES (1, 4, 200);]] 51 | [[INSERT INTO SP VALUES (1, 5, 100);]] 52 | [[INSERT INTO SP VALUES (1, 6, 100);]] 53 | [[INSERT INTO SP VALUES (2, 1, 300);]] 54 | [[INSERT INTO SP VALUES (2, 2, 400);]] 55 | [[INSERT INTO SP VALUES (3, 2, 200);]] 56 | [[INSERT INTO SP VALUES (4, 2, 200);]] 57 | [[INSERT INTO SP VALUES (4, 4, 300);]] 58 | [[INSERT INTO SP VALUES (4, 5, 400);]] 59 | } 60 | for stat in *statements 61 | (update db) stat 62 | 63 | teardown -> 64 | import close from grasp 65 | close db 66 | 67 | it "a simple query", -> 68 | for r in (query db) "select * from P" 69 | assert.is.truthy r.PNAME 70 | 71 | it "named parameters", -> 72 | for r in (query db) "select * from p where color = :color", color: "Red" 73 | assert.is.truthy r.PNAME 74 | 75 | it "positional parameters", -> 76 | for r in (query db) "select * from p where weight = ?2 AND color = ?1", {"Red", 12} 77 | assert.is.truthy r.PNAME 78 | 79 | it "pgmoon-like queries", -> 80 | assert.are.same {{ 81 | PNUM: 5 82 | PNAME: "Cam" 83 | COLOR: "Blue" 84 | WEIGHT: 12 85 | CITY: "Paris" 86 | }}, (squery db) "select * from p where pnum = 5;" 87 | assert.are.same { 88 | affected_rows: 1 89 | }, (squery db) "insert into p values (7, 'Screw', 'Green', 13, 'Madrid');" 90 | assert.is.true (squery db) "update p set WEIGHT = 12 where pnum = 8;" 91 | 92 | describe "prepared statements", -> 93 | import Statement from grasp 94 | stmt = (Statement db) "select * from p where color = :color" 95 | stmt2 = (Statement db) "select * from p where color = :color" 96 | 97 | it "binding", -> 98 | import bind from grasp 99 | do (bind stmt) color: "Red" 100 | for r in query stmt 101 | assert.is.truthy r.PNAME 102 | 103 | it "repeatedly", -> 104 | for r in query stmt2, color: "Red" 105 | assert.is.truthy r.PNAME 106 | for r in query stmt2, color: "Blue" 107 | assert.is.truthy r.PNAME 108 | 109 | it "transactions", -> 110 | import Transaction, queryone, update from grasp 111 | do (Transaction db) => 112 | (update @) "insert into P values (7, 'Washer', 'Grey', 5, 'Helsinki')" 113 | (update @) "insert into P values (8, 'Washer', 'Black', 7, 'Helsinki')" 114 | assert.is.truthy (queryone db) "select * from P where city = 'Helsinki'" -------------------------------------------------------------------------------- /spec/simple.moon: -------------------------------------------------------------------------------- 1 | sqlite = require "lsqlite3" 2 | db = sqlite.open ":memory:" 3 | db\exec [[CREATE TABLE P 4 | ( 5 | PNUM int NOT NULL PRIMARY KEY, 6 | PNAME varchar(18) NOT NULL, 7 | COLOR varchar(10) NOT NULL, 8 | WEIGHT decimal(4,1) NOT NULL, 9 | CITY varchar(20) NOT NULL, 10 | UNIQUE (PNAME, COLOR, CITY) 11 | );]] 12 | db\exec [[insert into P values (7, 'Washer', 'Grey', 5, 'Helsinki')]] 13 | 14 | stmt = db\prepare [[select * from P where city = 'Helsinki';]] 15 | for r in stmt\nrows! 16 | for k, v in pairs r do print k, v 17 | db\close! --------------------------------------------------------------------------------