rtree-sql.js
Version:
SQLite library with support for opening and writing databases, prepared statements, and more. This SQLite library is in pure javascript (compiled with emscripten).
509 lines (438 loc) • 19.9 kB
text/coffeescript
#@copyright Ophir LOJKINE
apiTemp = stackAlloc(4)
# Constants are defined in api-data.coffee
SQLite = {}
### Represents a prepared statement.
Prepared statements allow you to have a template sql string,
that you can execute multiple times with different parameters.
You can't instantiate this class directly, you have to use a [Database](Database.html)
object in order to create a statement.
**Warning**: When you close a database (using db.close()), all its statements are
closed too and become unusable.
@see Database.html#prepare-dynamic
@see https://en.wikipedia.org/wiki/Prepared_statement
###
class Statement
# Statements can't be created by the API user, only by Database::prepare
# @private
# @nodoc
constructor: (@stmt, @db) ->
@pos = 1 # Index of the leftmost parameter is 1
@allocatedmem = [] # Pointers to allocated memory, that need to be freed when the statemend is destroyed
### Bind values to the parameters, after having reseted the statement
SQL statements can have parameters, named *'?', '?NNN', ':VVV', '@VVV', '$VVV'*,
where NNN is a number and VVV a string.
This function binds these parameters to the given values.
*Warning*: ':', '@', and '$' are included in the parameters names
## Binding values to named parameters
@example Bind values to named parameters
var stmt = db.prepare("UPDATE test SET a=@newval WHERE id BETWEEN $mini AND $maxi");
stmt.bind({$mini:10, $maxi:20, '@newval':5});
- Create a statement that contains parameters like '$VVV', ':VVV', '@VVV'
- Call Statement.bind with an object as parameter
## Binding values to parameters
@example Bind values to anonymous parameters
var stmt = db.prepare("UPDATE test SET a=? WHERE id BETWEEN ? AND ?");
stmt.bind([5, 10, 20]);
- Create a statement that contains parameters like '?', '?NNN'
- Call Statement.bind with an array as parameter
## Value types
Javascript type | SQLite type
--- | ---
number | REAL, INTEGER
boolean | INTEGER
string | TEXT
Array, Uint8Array | BLOB
null | NULL
@see http://www.sqlite.org/datatype3.html
@see http://www.sqlite.org/lang_expr.html#varparam
@param values [Array,Object] The values to bind
@return [Boolean] true if it worked
@throw [String] SQLite Error
###
'bind' : (values) ->
if not @stmt then throw "Statement closed"
@['reset']()
if Array.isArray values then @bindFromArray values else @bindFromObject values
### Execute the statement, fetching the the next line of result,
that can be retrieved with [Statement.get()](#get-dynamic) .
@return [Boolean] true if a row of result available
@throw [String] SQLite Error
###
'step': ->
if not @stmt then throw "Statement closed"
@pos = 1
switch ret = sqlite3_step @stmt
when SQLite.ROW then return true
when SQLite.DONE then return false
else @db.handleError ret
# Internal methods to retrieve data from the results of a statement that has been executed
# @nodoc
getNumber: (pos = @pos++) -> sqlite3_column_double @stmt, pos
# @nodoc
getString: (pos = @pos++) -> sqlite3_column_text @stmt, pos
# @nodoc
getBlob: (pos = @pos++) ->
size = sqlite3_column_bytes @stmt, pos
ptr = sqlite3_column_blob @stmt, pos
result = new Uint8Array(size)
result[i] = HEAP8[ptr+i] for i in [0 ... size]
return result
### Get one row of results of a statement.
If the first parameter is not provided, step must have been called before get.
@param [Array,Object] Optional: If set, the values will be bound to the statement, and it will be executed
@return [Array<String,Number,Uint8Array,null>] One row of result
@example Print all the rows of the table test to the console
var stmt = db.prepare("SELECT * FROM test");
while (stmt.step()) console.log(stmt.get());
###
'get': (params) -> # Get all fields
if params? then @['bind'](params) and @['step']()
for field in [0 ... sqlite3_data_count(@stmt)]
switch sqlite3_column_type @stmt, field
when SQLite.INTEGER, SQLite.FLOAT then @getNumber field
when SQLite.TEXT then @getString field
when SQLite.BLOB then @getBlob field
else null
### Get the list of column names of a row of result of a statement.
@return [Array<String>] The names of the columns
@example
var stmt = db.prepare("SELECT 5 AS nbr, x'616200' AS data, NULL AS null_value;");
stmt.step(); // Execute the statement
console.log(stmt.getColumnNames()); // Will print ['nbr','data','null_value']
###
'getColumnNames' : () ->
sqlite3_column_name @stmt, i for i in [0 ... sqlite3_data_count(@stmt)]
### Get one row of result as a javascript object, associating column names with
their value in the current row.
@param [Array,Object] Optional: If set, the values will be bound to the statement, and it will be executed
@return [Object] The row of result
@see [Statement.get](#get-dynamic)
@example
var stmt = db.prepare("SELECT 5 AS nbr, x'616200' AS data, NULL AS null_value;");
stmt.step(); // Execute the statement
console.log(stmt.getAsObject()); // Will print {nbr:5, data: Uint8Array([1,2,3]), null_value:null}
###
'getAsObject': (params) ->
values = @['get'] params
names = @['getColumnNames']()
rowObject = {}
rowObject[name] = values[i] for name,i in names
return rowObject
### Shorthand for bind + step + reset
Bind the values, execute the statement, ignoring the rows it returns, and resets it
@param [Array,Object] Value to bind to the statement
###
'run': (values) ->
if values? then @['bind'](values)
@['step']()
@['reset']()
# Internal methods to bind values to parameters
# @private
# @nodoc
bindString: (string, pos = @pos++) ->
bytes = intArrayFromString(string)
@allocatedmem.push strptr = allocate bytes, 'i8', ALLOC_NORMAL
@db.handleError sqlite3_bind_text @stmt, pos, strptr, bytes.length-1, 0
return true
# @nodoc
bindBlob: (array, pos = @pos++) ->
@allocatedmem.push blobptr = allocate array, 'i8', ALLOC_NORMAL
@db.handleError sqlite3_bind_blob @stmt, pos, blobptr, array.length, 0
return true
# @private
# @nodoc
bindNumber: (num, pos = @pos++) ->
bindfunc = if num is (num|0) then sqlite3_bind_int else sqlite3_bind_double
@db.handleError bindfunc @stmt, pos, num
return true
# @nodoc
bindNull: (pos = @pos++) -> sqlite3_bind_blob(@stmt, pos, 0,0,0) is SQLite.OK
# Call bindNumber or bindString appropriatly
# @private
# @nodoc
bindValue: (val, pos = @pos++) ->
switch typeof val
when "string" then @bindString val, pos
when "number","boolean" then @bindNumber val+0, pos
when "object"
if val is null then @bindNull pos
else if val.length? then @bindBlob val, pos
else throw "Wrong API use : tried to bind a value of an unknown type (#{val})."
### Bind names and values of an object to the named parameters of the statement
@param [Object]
@private
@nodoc
###
bindFromObject : (valuesObj) ->
for name, value of valuesObj
num = sqlite3_bind_parameter_index @stmt, name
if num isnt 0 then @bindValue value, num
return true
### Bind values to numbered parameters
@param [Array]
@private
@nodoc
###
bindFromArray : (values) ->
@bindValue value, num+1 for value,num in values
return true
### Reset a statement, so that it's parameters can be bound to new values
It also clears all previous bindings, freeing the memory used by bound parameters.
###
'reset' : ->
@freemem()
sqlite3_clear_bindings(@stmt) is SQLite.OK and
sqlite3_reset(@stmt) is SQLite.OK
### Free the memory allocated during parameter binding
###
freemem : ->
_free mem while mem = @allocatedmem.pop()
return null
### Free the memory used by the statement
@return [Boolean] true in case of success
###
'free': ->
@freemem()
res = sqlite3_finalize(@stmt) is SQLite.OK
delete @db.statements[@stmt]
@stmt = NULL
return res
# Represents an SQLite database
class Database
# Open a new database either by creating a new one or opening an existing one,
# stored in the byte array passed in first argument
# @param data [Array<Integer>] An array of bytes representing an SQLite database file
constructor: (data) ->
@filename = 'dbfile_' + (0xffffffff*Math.random()>>>0)
if data? then FS.createDataFile '/', @filename, data, true, true
@handleError sqlite3_open @filename, apiTemp
@db = getValue(apiTemp, 'i32')
RegisterExtensionFunctions(@db)
@statements = {} # A list of all prepared statements of the database
@functions = {} # A list of all user function of the database (created by create_function call)
### Execute an SQL query, ignoring the rows it returns.
@param sql [String] a string containing some SQL text to execute
@param params [Array] (*optional*) When the SQL statement contains placeholders, you can pass them in here. They will be bound to the statement before it is executed.
If you use the params argument, you **cannot** provide an sql string that contains several
queries (separated by ';')
@example Insert values in a table
db.run("INSERT INTO test VALUES (:age, :name)", {':age':18, ':name':'John'});
@return [Database] The database object (useful for method chaining)
###
'run' : (sql, params) ->
if not @db then throw "Database closed"
if params
stmt = @['prepare'] sql, params
try stmt['step']()
finally stmt['free']()
else
@handleError sqlite3_exec @db, sql, 0, 0, apiTemp
return @
### Execute an SQL query, and returns the result.
This is a wrapper against Database.prepare, Statement.step, Statement.get,
and Statement.free.
The result is an array of result elements. There are as many result elements
as the number of statements in your sql string (statements are separated by a semicolon)
Each result element is an object with two properties:
'columns' : the name of the columns of the result (as returned by Statement.getColumnNames())
'values' : an array of rows. Each row is itself an array of values
## Example use
We have the following table, named *test* :
| id | age | name |
|:--:|:---:|:------:|
| 1 | 1 | Ling |
| 2 | 18 | Paul |
| 3 | 3 | Markus |
We query it like that:
```javascript
var db = new SQL.Database();
var res = db.exec("SELECT id FROM test; SELECT age,name FROM test;");
```
`res` is now :
```javascript
[
{columns: ['id'], values:[[1],[2],[3]]},
{columns: ['age','name'], values:[[1,'Ling'],[18,'Paul'],[3,'Markus']]}
]
```
@param sql [String] a string containing some SQL text to execute
@return [Array<QueryResults>] An array of results.
###
'exec': (sql) ->
if not @db then throw "Database closed"
stack = stackSave()
try
# Store the SQL string in memory. The string will be consumed, one statement
# at a time, by sqlite3_prepare_v2_sqlptr.
# Note that if we want to allocate as much memory as could _possibly_ be used, we can
# we allocate bytes equal to 4* the number of chars in the sql string.
# It would be faster, but this is probably a premature optimization
nextSqlPtr = allocateUTF8OnStack(sql)
# Used to store a pointer to the next SQL statement in the string
pzTail = stackAlloc(4)
results = []
while getValue(nextSqlPtr,'i8') isnt NULL
setValue apiTemp, 0, 'i32'
setValue pzTail, 0, 'i32'
@handleError sqlite3_prepare_v2_sqlptr @db, nextSqlPtr, -1, apiTemp, pzTail
pStmt = getValue apiTemp, 'i32' # pointer to a statement, or null
nextSqlPtr = getValue pzTail, 'i32'
if pStmt is NULL then continue # Empty statement
curresult = null
stmt = new Statement pStmt, this
try
while stmt['step']()
if curresult is null
curresult =
'columns' : stmt['getColumnNames']()
'values' : []
results.push curresult
curresult['values'].push stmt['get']()
finally
stmt['free']()
return results
finally
stackRestore stack
### Execute an sql statement, and call a callback for each row of result.
**Currently** this method is synchronous, it will not return until the callback has
been called on every row of the result. But this might change.
@param sql [String] A string of SQL text. Can contain placeholders that will be
bound to the parameters given as the second argument
@param params [Array<String,Number,null,Uint8Array>] (*optional*) Parameters to bind
to the query
@param callback [Function(Object)] A function that will be called on each row of result
@param done [Function] A function that will be called when all rows have been retrieved
@return [Database] The database object. Useful for method chaining
@example Read values from a table
db.each("SELECT name,age FROM users WHERE age >= $majority",
{$majority:18},
function(row){console.log(row.name + " is a grown-up.")}
);
###
'each' : (sql, params, callback, done) ->
if typeof params is 'function'
done = callback
callback = params
params = undefined
stmt = @['prepare'] sql, params
try
while stmt['step']()
callback(stmt['getAsObject']())
finally
stmt['free']()
if typeof done is 'function' then done()
### Prepare an SQL statement
@param sql [String] a string of SQL, that can contain placeholders ('?', ':VVV', ':AAA', '@AAA')
@param params [Array] (*optional*) values to bind to placeholders
@return [Statement] the resulting statement
@throw [String] SQLite error
###
'prepare': (sql, params) ->
setValue apiTemp, 0, 'i32'
@handleError sqlite3_prepare_v2 @db, sql, -1, apiTemp, NULL
pStmt = getValue apiTemp, 'i32' # pointer to a statement, or null
if pStmt is NULL then throw 'Nothing to prepare'
stmt = new Statement pStmt, this
if params? then stmt.bind params
@statements[pStmt] = stmt
return stmt
### Exports the contents of the database to a binary array
@return [Uint8Array] An array of bytes of the SQLite3 database file
###
'export': ->
stmt['free']() for _,stmt of @statements
removeFunction(func) for _,func of @functions
@functions={}
@handleError sqlite3_close_v2 @db
binaryDb = FS.readFile @filename, encoding:'binary'
@handleError sqlite3_open @filename, apiTemp
@db = getValue apiTemp, 'i32'
binaryDb
### Close the database, and all associated prepared statements.
The memory associated to the database and all associated statements
will be freed.
**Warning**: A statement belonging to a database that has been closed cannot
be used anymore.
Databases **must** be closed, when you're finished with them, or the
memory consumption will grow forever
###
'close': ->
stmt['free']() for _,stmt of @statements
removeFunction(func) for _,func of @functions
@functions={}
@handleError sqlite3_close_v2 @db
FS.unlink '/' + @filename
@db = null
### Analyze a result code, return null if no error occured, and throw
an error with a descriptive message otherwise
@nodoc
###
handleError: (returnCode) ->
if returnCode is SQLite.OK
null
else
errmsg = sqlite3_errmsg @db
throw new Error(errmsg)
### Returns the number of rows modified, inserted or deleted by the
most recently completed INSERT, UPDATE or DELETE statement on the
database Executing any other type of SQL statement does not modify
the value returned by this function.
@return [Number] the number of rows modified
###
'getRowsModified': -> sqlite3_changes(@db)
### Register a custom function with SQLite
@example Register a simple function
db.create_function("addOne", function(x) {return x+1;})
db.exec("SELECT addOne(1)") // = 2
@param name [String] the name of the function as referenced in SQL statements.
@param func [Function] the actual function to be executed.
###
'create_function': (name, func) ->
wrapped_func = (cx, argc, argv) ->
# Parse the args from sqlite into JS objects
args = []
for i in [0...argc]
value_ptr = getValue(argv+(4*i), 'i32')
value_type = sqlite3_value_type(value_ptr)
data_func = switch
when value_type == 1 then sqlite3_value_double
when value_type == 2 then sqlite3_value_double
when value_type == 3 then sqlite3_value_text
when value_type == 4 then (ptr) ->
size = sqlite3_value_bytes(ptr)
blob_ptr = sqlite3_value_blob(ptr)
blob_arg = new Uint8Array(size)
blob_arg[j] = HEAP8[blob_ptr+j] for j in [0 ... size]
blob_arg
else (ptr) -> null
arg = data_func(value_ptr)
args.push arg
# Invoke the user defined function with arguments from SQLite
try
result = func.apply(null, args)
catch error
sqlite3_result_error(cx,error,-1)
return
# Return the result of the user defined function to SQLite
switch typeof(result)
when 'boolean' then sqlite3_result_int(cx,if result then 1 else 0)
when 'number' then sqlite3_result_double(cx, result)
when 'string' then sqlite3_result_text(cx, result, -1, -1)
when 'object'
if result is null then sqlite3_result_null cx
else if result.length?
blobptr = allocate result, 'i8', ALLOC_NORMAL
sqlite3_result_blob(cx, blobptr,result.length, -1)
_free blobptr
else sqlite3_result_error(cx,"Wrong API use : tried to return a value of an unknown type (#{result}).",-1)
else sqlite3_result_null cx
return
if(name of @functions)
removeFunction(@functions[name])
delete @functions[name]
# Generate a pointer to the wrapped, user defined function, and register with SQLite.
func_ptr = addFunction(wrapped_func)
@functions[name]=func_ptr
@handleError sqlite3_create_function_v2 @db, name, func.length, SQLite.UTF8, 0, func_ptr, 0, 0, 0
return @