Skip to content

Commit 763d162

Browse files
committed
Merge pull request #9 from lovasoa/api
Api & prepared statements
2 parents 8d5f15a + 4c9d107 commit 763d162

File tree

7 files changed

+200116
-266
lines changed

7 files changed

+200116
-266
lines changed

Makefile

+18-13
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,32 @@ EMSCRIPTEN?=/usr/bin
44
EMCC=$(EMSCRIPTEN)/emcc -s RESERVED_FUNCTION_POINTERS=2 --closure 1 -O3 -s INLINING_LIMIT=10
55
CC=clang -O2
66
CFLAGS=-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DISABLE_LFS -DLONGDOUBLE_TYPE=double -DSQLITE_INT64_TYPE="long long int" -DSQLITE_THREADSAFE=0
7-
EXPORTED_FUNCTIONS="['_sqlite3_open', '_sqlite3_close', '_sqlite3_exec', '_sqlite3_free', '_sqlite3_column_count', ' _sqlite3_column_type', '_sqlite3_column_text', '_sqlite3_column_double', '_sqlite3_prepare_v2', '_sqlite3_step']"
7+
EXPORTED_FUNCTIONS="['_sqlite3_open', '_sqlite3_close', '_sqlite3_exec', '_sqlite3_free', '_sqlite3_data_count', '_sqlite3_column_type', '_sqlite3_column_text', '_sqlite3_column_double', '_sqlite3_prepare_v2', '_sqlite3_step', '_sqlite3_bind_text', '_sqlite3_bind_double', '_sqlite3_reset', '_sqlite3_finalize']"
88

99

10-
all: js/sql.js test/benchmark.js test/benchmark
10+
all: js/sql.js js/sql-api.js
1111

12-
js/sql.js: c/sqlite3.bc js/pre.js js/post.js
13-
$(EMCC) -s ASM_JS=1 $(CFLAGS) c/sqlite3.c --pre-js js/pre.js --post-js js/post.js -o js/sql.js -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)
12+
# Old version, without prepared statements
13+
js/sql.js: js/shell-pre.js js/sql-raw.js js/shell-post.js
14+
cat js/shell-pre.js js/sql-raw.js js/shell-post.js > js/sql.js
1415

15-
js/sql-faststart.js: c/sqlite3.bc js/pre.js js/post.js
16-
$(EMCC) -s ASM_JS=2 $(CFLAGS) c/sqlite3.c --pre-js js/pre.js --post-js js/post.js -o js/sql-faststart.js -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)
16+
js/sql-raw.js: c/sqlite3.bc js/pre.js js/post.js
17+
$(EMCC) -s ASM_JS=1 $(CFLAGS) c/sqlite3.c --pre-js js/pre.js --post-js js/post.js -o js/sql-raw.js -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)
18+
19+
# Object-oriented API
20+
js/sql-api.js: js/shell-pre.js js/sql-api-raw.js js/api-post.js
21+
cat js/shell-pre.js js/sql-api-raw.js js/api-post.js > js/sql-api.js
22+
23+
js/sql-api-raw.js: c/sqlite3.bc js/api.js
24+
$(EMSCRIPTEN)/emcc -s RESERVED_FUNCTION_POINTERS=2 $(CFLAGS) c/sqlite3.c --post-js js/api.js -o js/sql-api-raw.js -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)
25+
26+
js/api.js: js/api.coffee
27+
coffee -b -c js/api.coffee
1728

1829
c/sqlite3.bc: c/sqlite3.c
1930
# Generate llvm bitcode
2031
$(EMCC) $(CFLAGS) c/sqlite3.c -o c/sqlite3.bc
2132

22-
test/benchmark.js: c/sqlite3.c c/benchmark.c
23-
$(EMCC) $(CFLAGS) c/sqlite3.bc c/benchmark.c -o test/benchmark.js
24-
25-
test/benchmark: c/benchmark.c c/sqlite3.c
26-
$(CC) $(CFLAGS) -pthread c/sqlite3.c c/benchmark.c -o test/benchmark -ldl
27-
2833
clean:
29-
rm -rf js/sql.js test/benchmark.js test/benchmark
34+
rm -rf js/sql.js js/sql-api.js js/sql-raw.js c/sqlite3.bc
3035

README.md

+39-29
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,48 @@ SQLite is public domain, sql.js is MIT licensed.
99

1010
## Usage
1111

12-
```coffeescript
13-
Sql = require 'node-sqlite-purejs'
14-
Sql.open 'db/development.sqlite', {}, (err, db) ->
15-
throw err if err
16-
db.exec '''
17-
/* Demo DB */
18-
CREATE TABLE employees( id integer, name text,
19-
designation text, manager integer,
20-
hired_on date, salary integer,
21-
commission float, dept integer);
22-
23-
INSERT INTO employees VALUES (1,'JOHNSON','ADMIN',6,'12-17-1990',18000,NULL,4);
24-
INSERT INTO employees VALUES (2,'HARDING','MANAGER',9,'02-02-1998',52000,300,3);
25-
INSERT INTO employees VALUES (3,'TAFT','SALES I',2,'01-02-1996',25000,500,3);
26-
INSERT INTO employees VALUES (4,'HOOVER','SALES I',2,'04-02-1990',27000,NULL,3);
27-
INSERT INTO employees VALUES (5,'LINCOLN','TECH',6,'06-23-1994',22500,1400,4);
28-
INSERT INTO employees VALUES (6,'GARFIELD','MANAGER',9,'05-01-1993',54000,NULL,4);
29-
INSERT INTO employees VALUES (7,'POLK','TECH',6,'09-22-1997',25000,NULL,4);
30-
INSERT INTO employees VALUES (8,'GRANT','ENGINEER',10,'03-30-1997',32000,NULL,2);
31-
INSERT INTO employees VALUES (9,'JACKSON','CEO',NULL,'01-01-1990',75000,NULL,4);
32-
INSERT INTO employees VALUES (10,'FILLMORE','MANAGER',9,'08-09-1994',56000,NULL,2);
33-
INSERT INTO employees VALUES (11,'ADAMS','ENGINEER',10,'03-15-1996',34000,NULL,2);
34-
INSERT INTO employees VALUES (12,'WASHINGTON','ADMIN',6,'04-16-1998',18000,NULL,4);
35-
INSERT INTO employees VALUES (13,'MONROE','ENGINEER',10,'12-03-2000',30000,NULL,2);
36-
INSERT INTO employees VALUES (14,'ROOSEVELT','CPA',9,'10-12-1995',35000,NULL,1);
37-
'''
38-
39-
db.exec "SELECT * FROM employees WHERE designation = 'CEO';", (err, results) ->
40-
assert.deepEqual [{"columns":["id","name","designation","manager","hired_on","salary","commission","dept"],"values":[["9","JACKSON","CEO","","01-01-1990","75000","","4"]]}], results
12+
```javascript
13+
var sql = require('./js/sql-api.js');
14+
// or sql = window.SQL if you are in a browser
15+
16+
// Create a database
17+
var db = new sql.Database();
18+
// NOTE: You can also use new sql.Database(data) where
19+
// data is an Uint8Array representing an SQLite database file
20+
21+
// Execute some sql
22+
sqlstr = "CREATE TABLE hello (a int, b char);";
23+
sqlstr += "INSERT INTO hello VALUES (0, 'hello');"
24+
sqlstr += "INSERT INTO hello VALUES (1, 'world');"
25+
db.exec(sqlstr);
26+
27+
// Prepare an sql statement
28+
var stmt = db.prepare("SELECT * FROM hello WHERE a=? AND b=?");
29+
30+
// Bind values to the parameters
31+
stmt.bind([1, 'world']);
32+
33+
// Fetch the results of the query
34+
while (stmt.step()) console.log(stmt.get()); // Will print [1, 'world']
35+
36+
// Resets the statement, so it can be used again with other parameters
37+
stmt.reset()
38+
// Bind other values
39+
stmt.bind([0, 'hello']);
40+
while (stmt.step()) console.log(stmt.get()); // Will print [0, 'hello']
41+
42+
// free the memory used by the statement
43+
stmt.free();
44+
// You can not use your statement anymore once it has been freed.
45+
// But not freeing your statements causes memory leaks. You don't want that.
46+
47+
// Export the database to an Uint8Array containing the SQLite database file
48+
var binaryArray = db.export();
4149
```
4250

4351
## Differences from the original sql.js
52+
* Support for prepared statements
53+
* Cleaner API
4454
* More recent version of SQLite (3.8.4)
4555
* Compiled to asm.js (should be faster, at least on firefox)
4656
* Changed API. Results now have the form <code>[{'columns':[], values:[]}]</code>

js/api.coffee

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
apiTemp = Runtime.stackAlloc(4);
2+
3+
SQLite = {
4+
# Constants are defined below
5+
errorMessages : [] # Will contain all SQLite error descriptions sorted by error codes
6+
};
7+
8+
dataTemp = []
9+
callbackTemp = Runtime.addFunction (notUsed, argc, argv, colNames) ->
10+
curresult = if (dataTemp.length==0) then null else dataTemp[dataTemp.length-1]
11+
isNewResult = (curresult is null or argc isnt curresult.columns.length);
12+
curvalues = []
13+
curcolumns = []
14+
15+
for i in [0..argc]
16+
column = Pointer_stringify getValue colNames+i*Runtime.QUANTUM_SIZE, 'i32'
17+
value = Pointer_stringify getValue argv+i*Runtime.QUANTUM_SIZE, 'i32'
18+
curvalues.push value
19+
curcolumns.push column
20+
if not isNewResult and column isnt curresult.columns[i] then isNewResult = true;
21+
22+
if isNewResult
23+
dataTemp.push {
24+
'columns' : curcolumns
25+
'values' : [curvalues]
26+
}
27+
else
28+
curresult.values.push(curvalues);
29+
30+
class Statement
31+
constructor: (@stmt) ->
32+
@pos = 1 # Index of the leftmost parameter is 1
33+
step: ->
34+
@pos = 1
35+
ret = sqlite3_step @stmt
36+
if ret is SQLite.ROW then return true
37+
else if ret is SQLite.DONE then return false
38+
else throw 'SQLite error: ' + handleErrors ret
39+
getNumber: (pos = @pos++) -> sqlite3_column_double @stmt, pos
40+
getString: (pos = @pos++) -> sqlite3_column_text @stmt, pos
41+
get: -> # Get all fields
42+
for field in [0 ... sqlite3_data_count(@stmt)]
43+
type = sqlite3_column_type @stmt, field
44+
if type in [SQLite.INTEGER, SQLite.FLOAT] then @getNumber field
45+
else if type in [SQLite.TEXT, SQLite.BLOB] then @getString field
46+
else null
47+
bindString: (string, pos = @pos++) ->
48+
ret = sqlite3_bind_text @stmt, pos, string, -1, NULL
49+
err = handleErrors ret
50+
if err isnt null then throw 'SQLite error : ' + err
51+
bindNumber: (num, pos = @pos++) ->
52+
ret = sqlite3_bind_double @stmt, pos, num
53+
err = handleErrors ret
54+
if err isnt null then throw 'SQLite error : ' + err
55+
bindValue: (val, pos = @pos++) ->
56+
switch typeof val
57+
when "string" then @bindString val, pos
58+
when "number" then @bindNumber val, pos
59+
# Not binding a parameter is the same as binding it to NULL
60+
bind : (values) ->
61+
@bindValue v,i+1 for v,i in values # Index of the leftmost parameter is 1
62+
null
63+
reset : -> sqlite3_reset @stmt
64+
free: -> sqlite3_finalize @stmt
65+
66+
class Database
67+
# Open a new database:
68+
#create a new one or open an existing database stored in the byte array passed in first argument
69+
constructor: (data) ->
70+
@filename = 'dbfile_' + (0xffffffff*Math.random()>>>0)
71+
if data? then FS.createDataFile '/', @filename, data, true, true
72+
ret = sqlite3_open @filename, apiTemp
73+
if ret isnt SQLite.OK then throw 'SQLite error: ' + SQLite.errorMessages[ret]
74+
@db = getValue(apiTemp, 'i32')
75+
76+
# Close the database
77+
close: ->
78+
ret = sqlite3_close @db
79+
if ret isnt 0 then throw 'SQLite error: ' + SQLite_codes[ret].msg
80+
@db = null
81+
82+
# Execute an SQL query, and returns the result
83+
exec: (sql) ->
84+
if not @db then throw "Database closed"
85+
dataTemp = []
86+
setValue apiTemp, 0, 'i32'
87+
ret = sqlite3_exec @db, sql, callbackTemp, 0, apiTemp
88+
err = handleErrors ret, apiTemp
89+
if err isnt null then throw 'SQLite error : ' + err
90+
return dataTemp
91+
92+
# Prepare an SQL statement
93+
prepare: (sql) ->
94+
setValue apiTemp, 0, 'i32'
95+
ret = sqlite3_prepare_v2 @db, sql, -1, apiTemp, NULL
96+
err = handleErrors ret, NULL
97+
if err isnt null then throw 'SQLite error: ' + err
98+
pStmt = getValue apiTemp, 'i32' # pointer to a statement, or null
99+
if pStmt is NULL then throw 'Nothing to prepare'
100+
return new Statement(pStmt)
101+
102+
# Exports the contents of the database to a binary array
103+
export: -> new Uint8Array FS.root.contents[@filename].contents
104+
105+
handleErrors = (ret, errPtrPtr) ->
106+
if not errPtrPtr
107+
return if ret is SQLite.OK then null else SQLite.errorMessages[ret]
108+
else
109+
errPtr = getValue errPtrPtr, 'i32'
110+
if errPtr isnt NULL and errPtr isnt undefined
111+
msg = Pointer_stringify errPtr
112+
sqlite3_free errPtr
113+
return msg
114+
else return null
115+
116+
sqlite3_open = Module.cwrap 'sqlite3_open', 'number', ['string', 'number']
117+
sqlite3_close = Module.cwrap 'sqlite3_close', 'number', ['number'];
118+
sqlite3_exec = Module.cwrap 'sqlite3_exec', 'number', ['number', 'string', 'number', 'number', 'number']
119+
sqlite3_free = Module.cwrap 'sqlite3_free', '', ['number']
120+
121+
# Prepared statements
122+
## prepare
123+
sqlite3_prepare_v2 = Module.cwrap 'sqlite3_prepare_v2', 'number', ['number', 'string', 'number', 'number', 'number']
124+
## Bind parameters
125+
#int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
126+
sqlite3_bind_text = Module.cwrap 'sqlite3_bind_text', 'number', ['number', 'number', 'string', 'number', 'number']
127+
#int sqlite3_bind_double(sqlite3_stmt*, int, double);
128+
sqlite3_bind_double = Module.cwrap 'sqlite3_bind_double', 'number', ['number', 'number', 'number']
129+
130+
## Get values
131+
# int sqlite3_step(sqlite3_stmt*)
132+
sqlite3_step = Module.cwrap 'sqlite3_step', 'number', ['number']
133+
# int sqlite3_data_count(sqlite3_stmt *pStmt);
134+
sqlite3_data_count = Module.cwrap 'sqlite3_data_count', 'number', ['number']
135+
sqlite3_column_double = Module.cwrap 'sqlite3_column_double', 'number', ['number', 'number']
136+
sqlite3_column_text = Module.cwrap 'sqlite3_column_text', 'string', ['number', 'number']
137+
sqlite3_column_type = Module.cwrap 'sqlite3_column_type', 'number', ['number', 'number']
138+
# int sqlite3_reset(sqlite3_stmt *pStmt);
139+
sqlite3_reset = Module.cwrap 'sqlite3_reset', 'number', ['number']
140+
# int sqlite3_finalize(sqlite3_stmt *pStmt);
141+
sqlite3_finalize = Module.cwrap 'sqlite3_finalize', 'number', ['number']
142+
143+
# Global constants
144+
NULL = 0 # Null pointer
145+
146+
SQLite.OK=0
147+
SQLite.errorMessages[0]="Successful result"
148+
SQLite.ERROR=1
149+
SQLite.errorMessages[1]="SQL error or missing database"
150+
SQLite.INTERNAL=2
151+
SQLite.errorMessages[2]="Internal logic error in SQLite"
152+
SQLite.PERM=3
153+
SQLite.errorMessages[3]="Access permission denied"
154+
SQLite.ABORT=4
155+
SQLite.errorMessages[4]="Callback routine requested an abort"
156+
SQLite.BUSY=5
157+
SQLite.errorMessages[5]="The database file is locked"
158+
SQLite.LOCKED=6
159+
SQLite.errorMessages[6]="A table in the database is locked"
160+
SQLite.NOMEM=7
161+
SQLite.errorMessages[7]="A malloc() failed"
162+
SQLite.READONLY=8
163+
SQLite.errorMessages[8]="Attempt to write a readonly database"
164+
SQLite.INTERRUPT=9
165+
SQLite.errorMessages[9]="Operation terminated by sqlite3_interrupt()"
166+
SQLite.IOERR=10
167+
SQLite.errorMessages[10]="Some kind of disk I/O error occurred"
168+
SQLite.CORRUPT=11
169+
SQLite.errorMessages[11]="The database disk image is malformed"
170+
SQLite.NOTFOUND=12
171+
SQLite.errorMessages[12]="Unknown opcode in sqlite3_file_control()"
172+
SQLite.FULL=13
173+
SQLite.errorMessages[13]="Insertion failed because database is full"
174+
SQLite.CANTOPEN=14
175+
SQLite.errorMessages[14]="Unable to open the database file"
176+
SQLite.PROTOCOL=15
177+
SQLite.errorMessages[15]="Database lock protocol error"
178+
SQLite.EMPTY=16
179+
SQLite.errorMessages[16]="Database is empty"
180+
SQLite.SCHEMA=17
181+
SQLite.errorMessages[17]="The database schema changed"
182+
SQLite.TOOBIG=18
183+
SQLite.errorMessages[18]="String or BLOB exceeds size limit"
184+
SQLite.CONSTRAINT=19
185+
SQLite.errorMessages[19]="Abort due to constraint violation"
186+
SQLite.MISMATCH=20
187+
SQLite.errorMessages[20]="Data type mismatch"
188+
SQLite.MISUSE=21
189+
SQLite.errorMessages[21]="Library used incorrectly"
190+
SQLite.NOLFS=22
191+
SQLite.errorMessages[22]="Uses OS features not supported on host"
192+
SQLite.AUTH=23
193+
SQLite.errorMessages[23]="Authorization denied"
194+
SQLite.FORMAT=24
195+
SQLite.errorMessages[24]="Auxiliary database format error"
196+
SQLite.RANGE=25
197+
SQLite.errorMessages[25]="2nd parameter to sqlite3_bind out of range"
198+
SQLite.NOTADB=26
199+
SQLite.errorMessages[26]="File opened that is not a database file"
200+
SQLite.NOTICE=27
201+
SQLite.errorMessages[27]="Notifications from sqlite3_log()"
202+
SQLite.WARNING=28
203+
SQLite.errorMessages[28]="Warnings from sqlite3_log()"
204+
SQLite.ROW=100
205+
SQLite.errorMessages[100]="sqlite3_step() has another row ready"
206+
SQLite.DONE=101
207+
SQLite.errorMessages[101]="sqlite3_step() has finished executing"
208+
209+
# Data types
210+
SQLite.INTEGER=1
211+
SQLite.FLOAT=2
212+
SQLite.TEXT=3
213+
SQLite.BLOB=4
214+
SQLite.NULL=5
215+
216+
217+
# Export the API
218+
this['SQL'] = {'Database':Database}
219+
Module[i] = this['SQL'][i] for i of this['SQL']

js/post.js

-1
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,3 @@ Module['open'] = function(data) {
7171
};
7272

7373
this['SQL'] = Module;
74-

0 commit comments

Comments
 (0)