├── .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!
--------------------------------------------------------------------------------