From fa220a1c4c86c3aee8a7e5988526fad5c25d537e Mon Sep 17 00:00:00 2001 From: Ben Jolitz Date: Mon, 14 Mar 2016 19:53:51 -0700 Subject: [PATCH 1/2] Initial support/tests for SQLite flags and db dump This commit adds a pass through for specifying SQLite3 flags. This is an absolute requirement for being able to create in-memory DB representations. --- spec/database_spec.cr | 41 ++++++++++++++++++++++++++++++++++++ src/sqlite3/database.cr | 43 +++++++++++++++++++++++++++++++++++++- src/sqlite3/lib_sqlite3.cr | 6 ++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/spec/database_spec.cr b/spec/database_spec.cr index 6cdea3a..7a88167 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", + LibSQLite3::Flag::URI | LibSQLite3::Flag::CREATE | LibSQLite3::Flag::READWRITE | + LibSQLite3::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..c6d6309 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 @@ -21,6 +21,16 @@ class SQLite3::Database @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 : LibSQLite3::Flag) + code = LibSQLite3.open_v2(filename, out @db, flags, nil) + if code != 0 + raise Exception.new(@db) + end + @closed = false + end + # Creates a new Database object that opens the given file, yields it, and closes it at the end. def self.new(filename) db = new filename @@ -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/lib_sqlite3.cr b/src/sqlite3/lib_sqlite3.cr index 0c8b96e..b34c3c9 100644 --- a/src/sqlite3/lib_sqlite3.cr +++ b/src/sqlite3/lib_sqlite3.cr @@ -4,6 +4,7 @@ require "./type" lib LibSQLite3 type SQLite3 = Void* type Statement = Void* + type SQLite3Backup = Void* enum Flag READONLY = 0x00000001 # Ok for sqlite3_open_v2() @@ -29,6 +30,7 @@ lib LibSQLite3 end enum Code + OKAY = 0 ROW = 100 DONE = 101 end @@ -41,6 +43,10 @@ lib LibSQLite3 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 From c7260cd2cb4c47bc5dc609a69f02ce8a9075a19b Mon Sep 17 00:00:00 2001 From: Ben Jolitz Date: Wed, 16 Mar 2016 17:17:56 -0700 Subject: [PATCH 2/2] Seperate out LibSqlite3 enums as they do not change This allows LibSQLite3 to be changed without breaking anyone's code --- spec/database_spec.cr | 4 ++-- src/sqlite3/database.cr | 4 ++-- src/sqlite3/flags.cr | 22 ++++++++++++++++++++++ src/sqlite3/lib_sqlite3.cr | 33 +++++---------------------------- 4 files changed, 31 insertions(+), 32 deletions(-) create mode 100644 src/sqlite3/flags.cr diff --git a/spec/database_spec.cr b/spec/database_spec.cr index 7a88167..e777e0a 100644 --- a/spec/database_spec.cr +++ b/spec/database_spec.cr @@ -40,8 +40,8 @@ describe Database do 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", - LibSQLite3::Flag::URI | LibSQLite3::Flag::CREATE | LibSQLite3::Flag::READWRITE | - LibSQLite3::Flag::FULLMUTEX) + SQLite3::Flag::URI | SQLite3::Flag::CREATE | SQLite3::Flag::READWRITE | + SQLite3::Flag::FULLMUTEX) source_rows = db.execute "select * from person" db.dump(in_memory_db) diff --git a/src/sqlite3/database.cr b/src/sqlite3/database.cr index c6d6309..8f4efdc 100644 --- a/src/sqlite3/database.cr +++ b/src/sqlite3/database.cr @@ -14,7 +14,7 @@ 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 @@ -23,7 +23,7 @@ class SQLite3::Database # 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 : LibSQLite3::Flag) + def initialize(filename, flags : SQLite3::Flag) code = LibSQLite3.open_v2(filename, out @db, flags, nil) if code != 0 raise Exception.new(@db) 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 b34c3c9..e0fa1f9 100644 --- a/src/sqlite3/lib_sqlite3.cr +++ b/src/sqlite3/lib_sqlite3.cr @@ -6,46 +6,23 @@ lib LibSQLite3 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 - OKAY = 0 - 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 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