mirror of
https://gitea.invidious.io/iv-org/shard-crystal-sqlite3.git
synced 2024-08-15 00:53:26 +00:00
Merge master changes into db branch.
Refactor flags, add option to dump from one database connection to another. Merge commit '135f33d9eac82c63a48b14942c8eff647c070410' into db # Conflicts: # spec/database_spec.cr # src/sqlite3/lib_sqlite3.cr # src/sqlite3/result_set.cr # src/sqlite3/statement.cr
This commit is contained in:
commit
881ef79893
11 changed files with 138 additions and 229 deletions
10
.travis.yml
10
.travis.yml
|
@ -1,3 +1,9 @@
|
|||
language: crystal
|
||||
sudo: required
|
||||
dist: trusty
|
||||
before_install:
|
||||
- sudo apt-get autoremove sqlite3
|
||||
- sudo apt-get install python-software-properties
|
||||
- sudo apt-add-repository -y ppa:travis-ci/sqlite3
|
||||
- sudo apt-get -y update
|
||||
- sudo apt-cache show sqlite3
|
||||
- sudo apt-get install sqlite3=3.7.15.1-1~travis1
|
||||
- sudo sqlite3 -version
|
|
@ -1,5 +1,5 @@
|
|||
name: sqlite3
|
||||
version: 0.1.0
|
||||
version: 0.4.0
|
||||
|
||||
dependencies:
|
||||
db:
|
||||
|
|
47
spec/connection_spec.cr
Normal file
47
spec/connection_spec.cr
Normal file
|
@ -0,0 +1,47 @@
|
|||
require "./spec_helper"
|
||||
|
||||
private def dump(source, target)
|
||||
source.using_connection do |conn|
|
||||
conn = conn.as(SQLite3::Connection)
|
||||
target.using_connection do |backup_conn|
|
||||
backup_conn = backup_conn.as(SQLite3::Connection)
|
||||
conn.dump(backup_conn)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Connection do
|
||||
it "opens a database and then backs it up to another db" do
|
||||
with_db do |db|
|
||||
with_db("./test2.db") do |backup_db|
|
||||
db.exec "create table person (name string, age integer)"
|
||||
db.exec "insert into person values (\"foo\", 10)"
|
||||
|
||||
dump db, backup_db
|
||||
|
||||
backup_name = backup_db.scalar "select name from person"
|
||||
backup_age = backup_db.scalar "select age from person"
|
||||
source_name = db.scalar "select name from person"
|
||||
source_age = db.scalar "select age from person"
|
||||
|
||||
{backup_name, backup_age}.should eq({source_name, source_age})
|
||||
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|
|
||||
with_mem_db do |in_memory_db|
|
||||
db.exec "create table person (name string, age integer)"
|
||||
db.exec "insert into person values (\"foo\", 10)"
|
||||
dump db, in_memory_db
|
||||
|
||||
in_memory_db.scalar("select count(*) from person").should eq(1)
|
||||
in_memory_db.exec "insert into person values (\"bar\", 22)"
|
||||
dump in_memory_db, db
|
||||
|
||||
db.scalar("select count(*) from person").should eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,17 +1,5 @@
|
|||
require "./spec_helper"
|
||||
|
||||
DB_FILENAME = "./test.db"
|
||||
|
||||
def with_db(&block : DB::Database ->)
|
||||
DB.open "sqlite3:#{DB_FILENAME}", &block
|
||||
ensure
|
||||
File.delete(DB_FILENAME)
|
||||
end
|
||||
|
||||
def with_mem_db(&block : DB::Database ->)
|
||||
DB.open "sqlite3://%3Amemory%3A", &block
|
||||
end
|
||||
|
||||
def sql(s : String)
|
||||
"#{s.inspect}"
|
||||
end
|
||||
|
|
|
@ -2,3 +2,23 @@ require "spec"
|
|||
require "../src/sqlite3"
|
||||
|
||||
include SQLite3
|
||||
|
||||
DB_FILENAME = "./test.db"
|
||||
|
||||
def with_db(&block : DB::Database ->)
|
||||
File.delete(DB_FILENAME) rescue nil
|
||||
DB.open "sqlite3:#{DB_FILENAME}", &block
|
||||
ensure
|
||||
File.delete(DB_FILENAME)
|
||||
end
|
||||
|
||||
def with_db(name, &block : DB::Database ->)
|
||||
File.delete(name) rescue nil
|
||||
DB.open "sqlite3:#{name}", &block
|
||||
ensure
|
||||
File.delete(name)
|
||||
end
|
||||
|
||||
def with_mem_db(&block : DB::Database ->)
|
||||
DB.open "sqlite3://%3Amemory%3A", &block
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@ class SQLite3::Connection < DB::Connection
|
|||
def initialize(database)
|
||||
super
|
||||
filename = self.class.filename(database.uri)
|
||||
check LibSQLite3.open_v2(filename, out @db, (LibSQLite3::Flag::READWRITE | LibSQLite3::Flag::CREATE), nil)
|
||||
# TODO maybe enable Flag::URI to parse query string in the uri as additional flags
|
||||
check LibSQLite3.open_v2(filename, out @db, (Flag::READWRITE | Flag::CREATE), nil)
|
||||
end
|
||||
|
||||
def self.filename(uri : URI)
|
||||
|
@ -23,6 +24,24 @@ class SQLite3::Connection < DB::Connection
|
|||
LibSQLite3.close_v2(self)
|
||||
end
|
||||
|
||||
# Dump the database to another SQLite3 database. This can be used for backing up a SQLite3 Database
|
||||
# to disk or the opposite
|
||||
def dump(to : SQLite3::Connection)
|
||||
backup_item = LibSQLite3.backup_init(to.@db, "main", @db, "main")
|
||||
if backup_item.null?
|
||||
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
|
||||
|
||||
def to_unsafe
|
||||
@db
|
||||
end
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
# The Database class encapsulates single connection to an SQLite3 database. Its usage is very straightforward:
|
||||
#
|
||||
# ```
|
||||
# require "sqlite3"
|
||||
#
|
||||
# db = SQLite3::Database.new( "data.db" )
|
||||
# db.execute("select * from table") do |row|
|
||||
# p row
|
||||
# end
|
||||
# db.close
|
||||
# ```
|
||||
#
|
||||
# Lower level methods are also provided.
|
||||
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)
|
||||
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
|
||||
begin
|
||||
yield db
|
||||
ensure
|
||||
db.close
|
||||
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.
|
||||
#
|
||||
# Note that if any of the values passed to this are hashes, then the key/value pairs are each bound separately,
|
||||
# with the key being used as the name of the placeholder to bind the value to.
|
||||
#
|
||||
# Returns an `Array(Array(Value))`.
|
||||
def execute(sql, *binds)
|
||||
execute(sql, binds)
|
||||
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.
|
||||
#
|
||||
# Note that if any of the values passed to this are hashes, then the key/value pairs are each bound separately,
|
||||
# with the key being used as the name of the placeholder to bind the value to.
|
||||
#
|
||||
# Yields one `Array(Value)` for each result.
|
||||
def execute(sql, *binds, &block)
|
||||
execute(sql, binds) do |row|
|
||||
yield row
|
||||
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.
|
||||
#
|
||||
# Note that if any of the values passed to this are hashes, then the key/value pairs are each bound separately,
|
||||
# with the key being used as the name of the placeholder to bind the value to.
|
||||
#
|
||||
# Returns an `Array(Array(Value))`.
|
||||
def execute(sql, binds : Enumerable)
|
||||
rows = [] of Array(Value)
|
||||
execute(sql, binds) do |row|
|
||||
rows << row
|
||||
end
|
||||
rows
|
||||
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.
|
||||
#
|
||||
# Note that if any of the values passed to this are hashes, then the key/value pairs are each bound separately,
|
||||
# with the key being used as the name of the placeholder to bind the value to.
|
||||
#
|
||||
# Yields one `Array(Value)` for each result.
|
||||
def execute(sql, binds : Enumerable, &block)
|
||||
query(sql, binds) do |result_set|
|
||||
while result_set.next
|
||||
yield result_set.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A convenience method that returns the first row of a query result.
|
||||
def get_first_row(sql, *binds)
|
||||
get_first_row(sql, binds)
|
||||
end
|
||||
|
||||
# A convenience method that returns the first row of a query result.
|
||||
def get_first_row(sql, binds : Enumerable)
|
||||
query(sql, binds) do |result_set|
|
||||
if result_set.next
|
||||
return result_set.to_a
|
||||
else
|
||||
raise "no results"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A convenience method that returns the first value of the first row of a query result.
|
||||
def get_first_value(sql, *binds)
|
||||
get_first_value(sql, binds)
|
||||
end
|
||||
|
||||
# A convenience method that returns the first value of the first row of a query result.
|
||||
def get_first_value(sql, binds : Enumerable)
|
||||
query(sql, binds) do |result_set|
|
||||
if result_set.next
|
||||
return result_set[0]
|
||||
else
|
||||
raise "no results"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Executes a query and gives back a `ResultSet`.
|
||||
def query(sql, *binds)
|
||||
query(sql, binds)
|
||||
end
|
||||
|
||||
# Executes a query and yields a `ResultSet` that will be closed at the end of the given block.
|
||||
def query(sql, *binds, &block)
|
||||
query(sql, binds) do |result_set|
|
||||
yield result_set
|
||||
end
|
||||
end
|
||||
|
||||
# Executes a query and gives back a `ResultSet`.
|
||||
def query(sql, binds : Enumerable)
|
||||
prepare(sql).execute(binds)
|
||||
end
|
||||
|
||||
# Executes a query and yields a `ResultSet` that will be closed at the end of the given block.
|
||||
def query(sql, binds : Enumerable, &block)
|
||||
prepare(sql).execute(binds) do |result_set|
|
||||
yield result_set
|
||||
end
|
||||
end
|
||||
|
||||
# Prepares an sql statement. Returns a `Statement`.
|
||||
def prepare(sql)
|
||||
Statement.new(self, sql)
|
||||
end
|
||||
|
||||
# Obtains the unique row ID of the last row to be inserted by this Database instance.
|
||||
# This is an `Int64`.
|
||||
def last_insert_row_id
|
||||
LibSQLite3.last_insert_rowid(self)
|
||||
end
|
||||
|
||||
# Quotes the given string, making it safe to use in an SQL statement.
|
||||
# It replaces all instances of the single-quote character with two single-quote characters.
|
||||
def quote(string)
|
||||
string.gsub('\'', "''")
|
||||
end
|
||||
|
||||
# Returns `true` if this database instance has been closed (see `#close`).
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
# Closes this database.
|
||||
def close
|
||||
return if @closed
|
||||
|
||||
@closed = true
|
||||
|
||||
LibSQLite3.close_v2(@db)
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def finalize
|
||||
close
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def to_unsafe
|
||||
@db
|
||||
end
|
||||
end
|
30
src/sqlite3/flags.cr
Normal file
30
src/sqlite3/flags.cr
Normal file
|
@ -0,0 +1,30 @@
|
|||
@[Flags]
|
||||
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
|
||||
|
||||
module SQLite3
|
||||
# Same as doing SQLite3::Flag.flag(*values)
|
||||
macro flags(*values)
|
||||
::SQLite3::Flag.flags({{*values}})
|
||||
end
|
||||
end
|
|
@ -4,31 +4,10 @@ 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
|
||||
OKAY = 0
|
||||
ROW = 100
|
||||
DONE = 101
|
||||
end
|
||||
|
@ -36,11 +15,15 @@ lib LibSQLite3
|
|||
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
|
||||
|
|
|
@ -88,7 +88,7 @@ class SQLite3::ResultSet < DB::ResultSet
|
|||
when Type::TEXT ; String
|
||||
when Type::NULL ; Nil
|
||||
else
|
||||
raise "not implemented"
|
||||
raise Exception.new(@statement.connection)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Each of the possible types of an SQLite3 column.
|
||||
enum SQLite3::Type
|
||||
INTEGER = 1
|
||||
FLOAT = 2
|
||||
BLOB = 4
|
||||
NULL = 5
|
||||
TEXT = 3
|
||||
FLOAT = 2
|
||||
BLOB = 4
|
||||
NULL = 5
|
||||
TEXT = 3
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue