Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions sqlite3.carp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ primitive Carp types can be casted to appropriate SQLite types by using the
`to-sqlite3` interface.")
(deftype Type
(Null [])
(Integer [Int])
(Integer [Long])
(Floating [Double])
(Text [String])
(Blob [String]))
Expand All @@ -83,11 +83,20 @@ primitive Carp types can be casted to appropriate SQLite types by using the
(defmodule Type
(defmodule SQLiteColumn
(register nil (Fn [] SQLiteColumn) "SQLiteColumn_nil")
(register int (Fn [Int] SQLiteColumn) "SQLiteColumn_int")
(register int (Fn [Long] SQLiteColumn) "SQLiteColumn_int")
(register float (Fn [Double] SQLiteColumn) "SQLiteColumn_float")
(register text (Fn [String] SQLiteColumn) "SQLiteColumn_text")
(register blob (Fn [String] SQLiteColumn) "SQLiteColumn_blob"))

(defn = [a b]
(match-ref a
(Null) (match-ref b (Null) true _ false)
(Integer ai) (match-ref b (Integer bi) (= ai bi) _ false)
(Floating af) (match-ref b (Floating bf) (= af bf) _ false)
(Text as) (match-ref b (Text bs) (= as bs) _ false)
(Blob ab) (match-ref b (Blob bb) (= ab bb) _ false)))
(implements = SQLite3.Type.=)

(defn prn [s] (SQLite3.Type.str s))
(implements prn SQLite3.Type.prn)

Expand All @@ -102,7 +111,7 @@ primitive Carp types can be casted to appropriate SQLite types by using the
(defmodule SQLiteColumn
(register tag (Fn [&SQLiteColumn] Int) "SQLiteColumn_tag")

(register from-integer (Fn [SQLiteColumn] Int) "SQLiteColumn_from_int")
(register from-integer (Fn [SQLiteColumn] Long) "SQLiteColumn_from_int")
(register from-floating (Fn [SQLiteColumn] Double) "SQLiteColumn_from_float")
(register from-text (Fn [SQLiteColumn] String) "SQLiteColumn_from_str")

Expand Down Expand Up @@ -191,15 +200,15 @@ If it fails, we return an error message using `Result.Error`.")
(definterface to-sqlite3 (Fn [a] SQLite3.Type))

(defmodule Bool
(defn to-sqlite3 [b] (SQLite3.Type.Integer (if b 1 0)))
(defn to-sqlite3 [b] (SQLite3.Type.Integer (if b 1l 0l)))
(implements to-sqlite3 Bool.to-sqlite3))

(defmodule Int
(defn to-sqlite3 [i] (SQLite3.Type.Integer i))
(defn to-sqlite3 [i] (SQLite3.Type.Integer (Long.from-int i)))
(implements to-sqlite3 Int.to-sqlite3))

(defmodule Long
(defn to-sqlite3 [l] (SQLite3.Type.Integer (to-int (the Long l))))
(defn to-sqlite3 [l] (SQLite3.Type.Integer l))
(implements to-sqlite3 Long.to-sqlite3))

(defmodule Float
Expand Down
10 changes: 5 additions & 5 deletions sqlite3_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ typedef struct {
typedef struct {
int tag;
union {
int i;
int64_t i;
double f;
char* s;
};
Expand All @@ -19,7 +19,7 @@ int SQLiteColumn_tag(SQLiteColumn* col) {
return col->tag;
}

int SQLiteColumn_from_int(SQLiteColumn col) {
int64_t SQLiteColumn_from_int(SQLiteColumn col) {
return col.i;
}

Expand All @@ -37,7 +37,7 @@ SQLiteColumn SQLiteColumn_nil() {
return res;
}

SQLiteColumn SQLiteColumn_int(int i) {
SQLiteColumn SQLiteColumn_int(int64_t i) {
SQLiteColumn res;
res.tag = SQLITE_INTEGER;
res.i = i;
Expand Down Expand Up @@ -169,7 +169,7 @@ const char* SQLite3_exec_internal(sqlite3_stmt* s, SQLiteRows* rows) {
c->tag = sqlite3_column_type(s, i);
switch(c->tag) {
case SQLITE_INTEGER:
c->i = sqlite3_column_int(s, i);
c->i = sqlite3_column_int64(s, i);
break;
case SQLITE_FLOAT:
c->f = sqlite3_column_double(s, i);
Expand Down Expand Up @@ -230,7 +230,7 @@ const char* SQLite3_bind(sqlite3_stmt* s, Array* p) {
res = sqlite3_bind_null(s, i+1);
break;
case SQLITE_INTEGER:
res = sqlite3_bind_int(s, i+1, val.i);
res = sqlite3_bind_int64(s, i+1, (sqlite3_int64)val.i);
break;
case SQLITE_FLOAT:
res = sqlite3_bind_double(s, i+1, val.f);
Expand Down
248 changes: 248 additions & 0 deletions test/sqlite3.carp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
(load "../sqlite3.carp")
(load "Test.carp")
(use Test)

; ---------------------------------------------------------------------------
; Helpers
; ---------------------------------------------------------------------------

(defn open-memory [] (Result.unsafe-from-success (SQLite3.open ":memory:")))

; ---------------------------------------------------------------------------
; Tests
; ---------------------------------------------------------------------------

(deftest test
; =========================================================================
; Open / Close
; =========================================================================

(assert-true test
(Result.success? &(SQLite3.open ":memory:"))
"open in-memory database succeeds")

(assert-true test
(Result.error? &(SQLite3.open "/nonexistent/path/db"))
"open invalid path fails")

; =========================================================================
; DDL queries
; =========================================================================

(assert-true test
(let [db (open-memory)]
(let-do [r (SQLite3.query &db "CREATE TABLE t (x INT);" &[])]
(SQLite3.close db)
(Result.success? &r)))
"CREATE TABLE succeeds")

(assert-true test
(let [db (open-memory)]
(let-do [r (SQLite3.query &db "NOT VALID SQL" &[])]
(SQLite3.close db)
(Result.error? &r)))
"invalid SQL returns error")

; =========================================================================
; Insert and select - basic types
; =========================================================================

(assert-equal test
&(Result.Success
[[(SQLite3.Type.Integer 42l)
(SQLite3.Type.Text @"hello")
(SQLite3.Type.Floating 3.14)]])
&(let [db (open-memory)]
(let-do [r (do
(ignore
(SQLite3.query &db
"CREATE TABLE t (i INT, s TEXT, f REAL);"
&[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1, ?2, ?3);"
&[(to-sqlite3 42)
(to-sqlite3 @"hello")
(to-sqlite3 3.14)]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"insert and select Int, String, Double")

; =========================================================================
; Null handling
; =========================================================================

(assert-equal test
&(Result.Success [[(SQLite3.Type.Null)]])
&(let [db (open-memory)]
(let-do [r (do
(ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1);"
&[(SQLite3.Type.Null)]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"NULL round-trips correctly")

; =========================================================================
; Bool conversion
; =========================================================================

(assert-equal test
&(Result.Success [[(SQLite3.Type.Integer 1l)] [(SQLite3.Type.Integer 0l)]])
&(let [db (open-memory)]
(let-do [r (do
(ignore (SQLite3.query &db "CREATE TABLE t (b INT);" &[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1);"
&[(to-sqlite3 true)]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1);"
&[(to-sqlite3 false)]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"Bool to-sqlite3 stores 1 for true, 0 for false")

; =========================================================================
; Float conversion
; =========================================================================

(assert-equal test
&(Result.Success [[(SQLite3.Type.Floating 2.5)]])
&(let [db (open-memory)]
(let-do [r (do
(ignore (SQLite3.query &db "CREATE TABLE t (f REAL);" &[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1);"
&[(to-sqlite3 2.5f)]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"Float to-sqlite3 promotes to Double correctly")

; =========================================================================
; Long - large values (the bug fix)
; =========================================================================

(assert-equal test
&(Result.Success [[(SQLite3.Type.Integer 3000000000l)]])
&(let [db (open-memory)]
(let-do [r (do
(ignore (SQLite3.query &db "CREATE TABLE t (big INT);" &[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1);"
&[(to-sqlite3 3000000000l)]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"Long values > 2^31 round-trip without truncation")

(assert-equal test
&(Result.Success [[(SQLite3.Type.Integer -3000000000l)]])
&(let [db (open-memory)]
(let-do [r (do
(ignore (SQLite3.query &db "CREATE TABLE t (big INT);" &[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1);"
&[(to-sqlite3 -3000000000l)]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"negative Long values > 2^31 round-trip without truncation")

; =========================================================================
; Multiple rows
; =========================================================================

(assert-equal test
&(Result.Success
[[(SQLite3.Type.Integer 1l) (SQLite3.Type.Text @"a")]
[(SQLite3.Type.Integer 2l) (SQLite3.Type.Text @"b")]
[(SQLite3.Type.Integer 3l) (SQLite3.Type.Text @"c")]])
&(let [db (open-memory)]
(let-do [r (do
(ignore
(SQLite3.query &db "CREATE TABLE t (id INT, name TEXT);" &[]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1, ?2);"
&[(to-sqlite3 1) (to-sqlite3 @"a")]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1, ?2);"
&[(to-sqlite3 2) (to-sqlite3 @"b")]))
(ignore
(SQLite3.query &db
"INSERT INTO t VALUES (?1, ?2);"
&[(to-sqlite3 3) (to-sqlite3 @"c")]))
(SQLite3.query &db "SELECT * FROM t ORDER BY id;" &[]))]
(SQLite3.close db)
r))
"multiple rows returned correctly")

; =========================================================================
; Empty result set
; =========================================================================

(assert-equal test
&(Result.Success (the (Array (Array SQLite3.Type)) []))
&(let [db (open-memory)]
(let-do [r (do
(ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[]))
(SQLite3.query &db "SELECT * FROM t;" &[]))]
(SQLite3.close db)
r))
"empty result set returns empty array")

; =========================================================================
; Prepared statement parameters
; =========================================================================

(assert-equal test
&(Result.Success [[(SQLite3.Type.Integer 2l) (SQLite3.Type.Text @"bob")]])
&(let [db (open-memory)]
(let-do [r (do
(ignore
(SQLite3.query &db "CREATE TABLE t (id INT, name TEXT);" &[]))
(ignore
(SQLite3.query &db "INSERT INTO t VALUES (1, 'alice');" &[]))
(ignore
(SQLite3.query &db "INSERT INTO t VALUES (2, 'bob');" &[]))
(ignore
(SQLite3.query &db "INSERT INTO t VALUES (3, 'carol');" &[]))
(SQLite3.query &db
"SELECT * FROM t WHERE id = ?1;"
&[(to-sqlite3 2)]))]
(SQLite3.close db)
r))
"parameterized WHERE clause works")

; =========================================================================
; Type.str / show
; =========================================================================

(assert-equal test
"(Integer 7)"
&(str &(SQLite3.Type.Integer 7l))
"Type.Integer str")

(assert-equal test
"(Text @\"hi\")"
&(str &(SQLite3.Type.Text @"hi"))
"Type.Text str")

(assert-equal test
"(Floating 1.5)"
&(str &(SQLite3.Type.Floating 1.5))
"Type.Floating str")

(assert-equal test "(Null)" &(str &(SQLite3.Type.Null)) "Type.Null str"))
Loading