Merge pull request #6 from benjolitz/flag_support_and_dumping

Initial support/tests for SQLite flags and db dump
This commit is contained in:
Ary Borenszweig 2016-03-17 10:53:10 -03:00
commit db48a03797
4 changed files with 114 additions and 27 deletions

View file

@ -8,6 +8,12 @@ ensure
File.delete(DB_FILENAME) File.delete(DB_FILENAME)
end end
private def with_db(name)
yield Database.new name
ensure
File.delete(name)
end
describe Database do describe Database do
it "opens a database" do it "opens a database" do
with_db do |db| with_db do |db|
@ -15,6 +21,41 @@ describe Database do
end end
end end
it "opens a database and then backs it up to another db" do
with_db do |db|
db.execute "create table person (name string, age integer)"
db.execute "insert into person values (\"foo\", 10)"
with_db("./test2.db") do |backup_database|
db.dump(backup_database)
backup_rows = backup_database.execute "select * from person"
source_rows = db.execute "select * from person"
backup_rows.should eq(source_rows)
end
end
end
it "opens a database, inserts records, dumps to an in-memory db, insers some more, then dumps to the source" do
with_db do |db|
db.execute "create table person (name string, age integer)"
db.execute "insert into person values (\"foo\", 10)"
in_memory_db = Database.new("file:memdb1?mode=memory&cache=shared",
SQLite3::Flag::URI | SQLite3::Flag::CREATE | SQLite3::Flag::READWRITE |
SQLite3::Flag::FULLMUTEX)
source_rows = db.execute "select * from person"
db.dump(in_memory_db)
in_memory_db_rows = in_memory_db.execute "select * from person"
in_memory_db_rows.should eq(source_rows)
in_memory_db.execute "insert into person values (\"bar\", 22)"
in_memory_db.dump(db)
in_memory_db_rows = in_memory_db.execute "select * from person"
source_rows = db.execute "select * from person"
in_memory_db_rows.should eq(source_rows)
end
end
[nil, 1, 1_i64, "hello", 1.5, 1.5_f32].each do |value| [nil, 1, 1_i64, "hello", 1.5, 1.5_f32].each do |value|
it "executes and select #{value}" do it "executes and select #{value}" do
with_db(&.execute("select #{value ? value.inspect : "null"}")).should eq([[value]]) with_db(&.execute("select #{value ? value.inspect : "null"}")).should eq([[value]])

View file

@ -3,7 +3,7 @@
# ``` # ```
# require "sqlite3" # require "sqlite3"
# #
# db = SQLite3::Database.new( "data.db" ) # db = SQLite3::Database.new("data.db")
# db.execute("select * from table") do |row| # db.execute("select * from table") do |row|
# p row # p row
# end # end
@ -14,7 +14,17 @@
class SQLite3::Database class SQLite3::Database
# Creates a new Database object that opens the given file. # Creates a new Database object that opens the given file.
def initialize(filename) def initialize(filename)
code = LibSQLite3.open_v2(filename, out @db, (LibSQLite3::Flag::READWRITE | LibSQLite3::Flag::CREATE), nil) code = LibSQLite3.open_v2(filename, out @db, (SQLite3::Flag::READWRITE | SQLite3::Flag::CREATE), nil)
if code != 0
raise Exception.new(@db)
end
@closed = false
end
# Allows for initialization with specific flags. Primary use case is to allow
# for sqlite3 URI opening and in memory DB operations.
def initialize(filename, flags : SQLite3::Flag)
code = LibSQLite3.open_v2(filename, out @db, flags, nil)
if code != 0 if code != 0
raise Exception.new(@db) raise Exception.new(@db)
end end
@ -31,6 +41,37 @@ class SQLite3::Database
end end
end end
# Dump the database to another SQLite3 instance. This can be used for backing up a SQLite3::Database
# to disk or the opposite
# Example:
# ```
# source_database = SQLite3::Database.new("mydatabase.db")
# in_memory_db = SQLite3::Database.new(
# "file:memdb1?mode=memory&cache=shared",
# LibSQLite3::Flag::URI | LibSQLite3::Flag::CREATE | LibSQLite3::Flag::READWRITE |
# LibSQLite3::Flag::FULLMUTEX)
# source_database.dump(in_memory_db)
# source_database.close()
# in_memory_db.exectute { |row|
# ...
# }
# ```
def dump(to : SQLite3::Database)
backup_item = LibSQLite3.backup_init(to.@db, "main", @db, "main")
if backup_item.nil?
raise Exception.new(to.@db)
end
code = LibSQLite3.backup_step(backup_item, -1)
if code != LibSQLite3::Code::DONE
raise Exception.new(to.@db)
end
code = LibSQLite3.backup_finish(backup_item)
if code != LibSQLite3::Code::OKAY
raise Exception.new(to.@db)
end
end
# Executes the given SQL statement. If additional parameters are given, they are treated as bind variables, # Executes the given SQL statement. If additional parameters are given, they are treated as bind variables,
# and are bound to the placeholders in the query. # and are bound to the placeholders in the query.
# #

22
src/sqlite3/flags.cr Normal file
View file

@ -0,0 +1,22 @@
enum SQLite3::Flag
READONLY = 0x00000001 # Ok for sqlite3_open_v2()
READWRITE = 0x00000002 # Ok for sqlite3_open_v2()
CREATE = 0x00000004 # Ok for sqlite3_open_v2()
DELETEONCLOSE = 0x00000008 # VFS only
EXCLUSIVE = 0x00000010 # VFS only
AUTOPROXY = 0x00000020 # VFS only
URI = 0x00000040 # Ok for sqlite3_open_v2()
MEMORY = 0x00000080 # Ok for sqlite3_open_v2()
MAIN_DB = 0x00000100 # VFS only
TEMP_DB = 0x00000200 # VFS only
TRANSIENT_DB = 0x00000400 # VFS only
MAIN_JOURNAL = 0x00000800 # VFS only
TEMP_JOURNAL = 0x00001000 # VFS only
SUBJOURNAL = 0x00002000 # VFS only
MASTER_JOURNAL = 0x00004000 # VFS only
NOMUTEX = 0x00008000 # Ok for sqlite3_open_v2()
FULLMUTEX = 0x00010000 # Ok for sqlite3_open_v2()
SHAREDCACHE = 0x00020000 # Ok for sqlite3_open_v2()
PRIVATECACHE = 0x00040000 # Ok for sqlite3_open_v2()
WAL = 0x00080000 # VFS only
end

View file

@ -4,43 +4,26 @@ require "./type"
lib LibSQLite3 lib LibSQLite3
type SQLite3 = Void* type SQLite3 = Void*
type Statement = Void* type Statement = Void*
type SQLite3Backup = Void*
enum Flag
READONLY = 0x00000001 # Ok for sqlite3_open_v2()
READWRITE = 0x00000002 # Ok for sqlite3_open_v2()
CREATE = 0x00000004 # Ok for sqlite3_open_v2()
DELETEONCLOSE = 0x00000008 # VFS only
EXCLUSIVE = 0x00000010 # VFS only
AUTOPROXY = 0x00000020 # VFS only
URI = 0x00000040 # Ok for sqlite3_open_v2()
MEMORY = 0x00000080 # Ok for sqlite3_open_v2()
MAIN_DB = 0x00000100 # VFS only
TEMP_DB = 0x00000200 # VFS only
TRANSIENT_DB = 0x00000400 # VFS only
MAIN_JOURNAL = 0x00000800 # VFS only
TEMP_JOURNAL = 0x00001000 # VFS only
SUBJOURNAL = 0x00002000 # VFS only
MASTER_JOURNAL = 0x00004000 # VFS only
NOMUTEX = 0x00008000 # Ok for sqlite3_open_v2()
FULLMUTEX = 0x00010000 # Ok for sqlite3_open_v2()
SHAREDCACHE = 0x00020000 # Ok for sqlite3_open_v2()
PRIVATECACHE = 0x00040000 # Ok for sqlite3_open_v2()
WAL = 0x00080000 # VFS only
end
enum Code enum Code
ROW = 100 OKAY = 0
ROW = 100
DONE = 101 DONE = 101
end end
alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32 alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32
fun open = sqlite3_open_v2(filename : UInt8*, db : SQLite3*) : Int32 fun open = sqlite3_open_v2(filename : UInt8*, db : SQLite3*) : Int32
fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags: Flag, zVfs : UInt8*) : Int32 fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags : SQLite3::Flag, zVfs : UInt8*) : Int32
fun errcode = sqlite3_errcode(SQLite3) : Int32 fun errcode = sqlite3_errcode(SQLite3) : Int32
fun errmsg = sqlite3_errmsg(SQLite3) : UInt8* fun errmsg = sqlite3_errmsg(SQLite3) : UInt8*
fun backup_init = sqlite3_backup_init(SQLite3, UInt8*, SQLite3, UInt8*) : SQLite3Backup
fun backup_step = sqlite3_backup_step(SQLite3Backup, Int8) : Code
fun backup_finish = sqlite3_backup_finish(SQLite3Backup) : Code
fun prepare_v2 = sqlite3_prepare_v2(db : SQLite3, zSql : UInt8*, nByte : Int32, ppStmt : Statement*, pzTail : UInt8**) : Int32 fun prepare_v2 = sqlite3_prepare_v2(db : SQLite3, zSql : UInt8*, nByte : Int32, ppStmt : Statement*, pzTail : UInt8**) : Int32
fun step = sqlite3_step(stmt : Statement) : Int32 fun step = sqlite3_step(stmt : Statement) : Int32
fun column_count = sqlite3_column_count(stmt : Statement) : Int32 fun column_count = sqlite3_column_count(stmt : Statement) : Int32