Skip to content

Commit 6e0aec1

Browse files
committed
Fix data type conversion in user-defined functions
1 parent f63e05c commit 6e0aec1

File tree

4 files changed

+110
-10
lines changed

4 files changed

+110
-10
lines changed

src/api.coffee

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ class Database
459459
value_ptr = getValue(argv+(4*i), 'i32')
460460
value_type = sqlite3_value_type(value_ptr)
461461
data_func = switch
462-
when value_type == 1 then sqlite3_value_int
462+
when value_type == 1 then sqlite3_value_double
463463
when value_type == 2 then sqlite3_value_double
464464
when value_type == 3 then sqlite3_value_text
465465
when value_type == 4 then (ptr) ->
@@ -474,15 +474,26 @@ class Database
474474
args.push arg
475475

476476
# Invoke the user defined function with arguments from SQLite
477-
result = func.apply(null, args)
478-
479-
# Return the result of the user defined function to SQLite
480-
if not result
481-
sqlite3_result_null cx
482-
else
483-
switch typeof(result)
484-
when 'number' then sqlite3_result_double(cx, result)
485-
when 'string' then sqlite3_result_text(cx, result, -1, -1)
477+
try
478+
result = func.apply(null, args)
479+
catch error
480+
sqlite3_result_error(cx,error,-1)
481+
return
482+
483+
# Return the result of the user defined function to SQLite
484+
switch typeof(result)
485+
when 'boolean' then sqlite3_result_int(cx,if result then 1 else 0)
486+
when 'number' then sqlite3_result_double(cx, result)
487+
when 'string' then sqlite3_result_text(cx, result, -1, -1)
488+
when 'object'
489+
if result is null then sqlite3_result_null cx
490+
else if result.length?
491+
blobptr = allocate result, 'i8', ALLOC_NORMAL
492+
sqlite3_result_blob(cx, blobptr,result.length, -1)
493+
_free blobptr
494+
else sqlite3_result_error(cx,"Wrong API use : tried to return a value of an unknown type (#{result}).",-1)
495+
else sqlite3_result_null cx
496+
return
486497
if(name of @functions)
487498
removeFunction(@functions[name])
488499
delete @functions[name]

src/exported_functions.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,9 @@
3434
"_sqlite3_result_double",
3535
"_sqlite3_result_null",
3636
"_sqlite3_result_text",
37+
"_sqlite3_result_blob",
38+
"_sqlite3_result_int",
39+
"_sqlite3_result_int64",
40+
"_sqlite3_result_error",
3741
"_RegisterExtensionFunctions"
3842
]

src/exports.coffee

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ sqlite3_value_double = Module['cwrap'] 'sqlite3_value_double', 'number', ['numbe
5353
sqlite3_result_double = Module['cwrap'] 'sqlite3_result_double', '', ['number', 'number']
5454
sqlite3_result_null = Module['cwrap'] 'sqlite3_result_null', '', ['number']
5555
sqlite3_result_text = Module['cwrap'] 'sqlite3_result_text', '', ['number', 'string', 'number', 'number']
56+
sqlite3_result_blob = Module['cwrap'] 'sqlite3_result_blob', '', ['number', 'number', 'number', 'number']
57+
sqlite3_result_int = Module['cwrap'] 'sqlite3_result_int', '', ['number','number']
58+
sqlite3_result_int64 = Module['cwrap'] 'sqlite3_result_int64', '', ['number', 'number']
59+
sqlite3_result_error = Module['cwrap'] 'sqlite3_result_error', '', ['number', 'string', 'number']
5660
RegisterExtensionFunctions = Module['cwrap'] 'RegisterExtensionFunctions', 'number', ['number']
5761

5862
# Export the API

test/test_functions.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,87 @@ exports.test = function(SQL, assert){
5050
db.create_function("addOne", function (x) { return x + 1;} );
5151
result = db.exec("SELECT addOne(1);");
5252
assert.equal(result[0]["values"][0][0], 2, "Accepts anonymous functions");
53+
54+
// Test api support of different sqlite types and special values
55+
db.create_function("identityFunction", function (x) { return x;} );
56+
var verbose=false;
57+
function canHandle(testData)
58+
{
59+
let result={};
60+
let ok=true;
61+
let sql_value=("sql_value" in testData)?testData.sql_value:(""+testData.value);
62+
function simpleEqual(a, b) {return a===b;}
63+
let value_equal=("equal" in testData)?testData.equal:simpleEqual;
64+
db.create_function("CheckTestValue", function (x) {return value_equal(testData.value,x)?12345:5678;});
65+
db.create_function("GetTestValue", function () {return testData.value; });
66+
// Check sqlite to js value conversion
67+
result = db.exec("SELECT CheckTestValue("+sql_value+")==12345");
68+
if(result[0]["values"][0][0]!=1)
69+
{
70+
if(verbose)
71+
assert.ok(false, "Can accept "+testData.info);
72+
ok=false;
73+
}
74+
// Check js to sqlite value conversion
75+
result = db.exec("SELECT GetTestValue()");
76+
if(!value_equal(result[0]["values"][0][0],testData.value))
77+
{
78+
if(verbose)
79+
assert.ok(false, "Can return "+testData.info);
80+
ok=false;
81+
}
82+
// Check sqlite to sqlite value conversion (identityFunction(x)==x)
83+
if(sql_value!=="null")
84+
{
85+
result = db.exec("SELECT identityFunction("+sql_value+")="+sql_value);
86+
}else
87+
{
88+
result = db.exec("SELECT identityFunction("+sql_value+") is null");
89+
}
90+
if(result[0]["values"][0][0]!=1)
91+
{
92+
if(verbose)
93+
assert.ok(false, "Can pass "+testData.info);
94+
ok=false;
95+
}
96+
return ok;
97+
}
98+
99+
function numberEqual(a, b) {
100+
return (+a)===(+b);
101+
}
102+
103+
function blobEqual(a, b) {
104+
if(((typeof a)!="object")||(!a)||((typeof b)!="object")||(!b)) return false;
105+
if (a.byteLength !== b.byteLength) return false;
106+
return a.every((val, i) => val === b[i]);
107+
}
108+
109+
[
110+
{info:"null",value:null}, // sqlite special value null
111+
{info:"false",value:false,sql_value:"0",equal:numberEqual}, // sqlite special value (==false)
112+
{info:"true", value:true,sql_value:"1",equal:numberEqual}, // sqlite special value (==true)
113+
{info:"integer 0",value:0}, // sqlite special value (==false)
114+
{info:"integer 1",value:1}, // sqlite special value (==true)
115+
{info:"integer -1",value:-1},
116+
{info:"long integer 5e+9",value:5000000000}, // int64
117+
{info:"long integer -5e+9",value:-5000000000}, // negative int64
118+
{info:"double",value:0.5},
119+
{info:"string",value:"Test",sql_value:"'Test'"},
120+
{info:"empty string",value:"",sql_value:"''"},
121+
{info:"unicode string",value:"\uC7B8",sql_value:"CAST(x'EC9EB8' AS TEXT)"}, // unicode-hex: C7B8 utf8-hex: EC9EB8
122+
{info:"blob",value:new Uint8Array([0xC7,0xB8]),sql_value:"x'C7B8'",equal:blobEqual},
123+
{info:"empty blob",value:new Uint8Array([]),sql_value:"x''",equal:blobEqual}
124+
].forEach(function(testData)
125+
{
126+
assert.ok(canHandle(testData),"Can handle "+testData.info);
127+
});
128+
129+
db.create_function("throwFunction", function () { throw "internal exception"; return 5;} );
130+
assert.throws(function(){db.exec("SELECT throwFunction()");},/internal exception/, "Can handle internal exceptions");
131+
132+
db.create_function("customeObjectFunction", function () { return {test:123};} );
133+
assert.throws(function(){db.exec("SELECT customeObjectFunction()");},/Wrong API use/, "Reports wrong API use");
53134

54135
db.close();
55136
};

0 commit comments

Comments
 (0)