diff --git a/spec/database_spec.cr b/spec/database_spec.cr index 6cdea3a..e777e0a 100644 --- a/spec/database_spec.cr +++ b/spec/database_spec.cr @@ -8,6 +8,12 @@ ensure File.delete(DB_FILENAME) end +private def with_db(name) + yield Database.new name +ensure + File.delete(name) +end + describe Database do it "opens a database" do with_db do |db| @@ -15,6 +21,41 @@ describe Database do 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| it "executes and select #{value}" do with_db(&.execute("select #{value ? value.inspect : "null"}")).should eq([[value]]) diff --git a/src/sqlite3/database.cr b/src/sqlite3/database.cr index e05a8f4..8f4efdc 100644 --- a/src/sqlite3/database.cr +++ b/src/sqlite3/database.cr @@ -3,7 +3,7 @@ # ``` # require "sqlite3" # -# db = SQLite3::Database.new( "data.db" ) +# db = SQLite3::Database.new("data.db") # db.execute("select * from table") do |row| # p row # end @@ -14,7 +14,17 @@ class SQLite3::Database # Creates a new Database object that opens the given file. 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 raise Exception.new(@db) end @@ -31,6 +41,37 @@ class SQLite3::Database 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, # and are bound to the placeholders in the query. # diff --git a/src/sqlite3/flags.cr b/src/sqlite3/flags.cr new file mode 100644 index 0000000..9bf9cc8 --- /dev/null +++ b/src/sqlite3/flags.cr @@ -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 diff --git a/src/sqlite3/lib_sqlite3.cr b/src/sqlite3/lib_sqlite3.cr index 0c8b96e..e0fa1f9 100644 --- a/src/sqlite3/lib_sqlite3.cr +++ b/src/sqlite3/lib_sqlite3.cr @@ -4,43 +4,26 @@ require "./type" lib LibSQLite3 type SQLite3 = Void* type Statement = 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 + type SQLite3Backup = Void* enum Code - ROW = 100 + OKAY = 0 + ROW = 100 DONE = 101 end alias Callback = (Void*, Int32, UInt8**, UInt8**) -> 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 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 step = sqlite3_step(stmt : Statement) : Int32 fun column_count = sqlite3_column_count(stmt : Statement) : Int32