Compare commits

...

19 Commits

Author SHA1 Message Date
Brian J. Cardiff c58cea290c
Release 0.21.0, Update to crystal-db ~> 0.13 (#94)
* Update crystal-db ~> 0.13.0

* Bump version to 0.21.0
2023-12-12 22:15:47 -03:00
Brian J. Cardiff 1f2a1cd3cb
Release 0.20.0 (#92) 2023-06-23 16:29:58 -03:00
Brian J. Cardiff c08fc2befc
Update to crystal-db 0.12.0 (refactor connection factory) (#91)
* Refactor connection builder

* Update for ConnectionBuilder

* Update to crystal-db ~> 0.12.0

* run crystal tool format
2023-06-23 15:36:08 -03:00
Brian J. Cardiff 79615e6c85
Reset before close (#90) 2023-04-24 12:53:56 -03:00
Luis Lavena 84857724c1
Automatically set PRAGMAs using connection query params (#85)
* Automatically set PRAGMAs using connection query params

Introduce the flexibility to adjust certain PRAGMAs of the SQLite3
connection without having to hardcode those in your codebase (and wait
for compilation).

This allows applications to use `DATABASE_URL` to dynamically fine tune
their SQLite3 configuration.

The change complements `#setup_connection` that offers, via code, the
option to perform queries on setup of each connection.

Only a few PRAGMAs necessary to allow more performant concurrent reads
and reduce write locking.

These pragmas are detected and combined in a single SQL string to reduce
to 1 the number of calls to `sqlite3_exec` C function.

There is no validation of supplied values as SQLite3 automatically
ignores incorrect values for these pragmas.

Closes #84

References:
- https://www.sqlite.org/pragma.html

* Simplify PRAGMA mapping and detection

No longer prefix PRAGMAS with `_`, so the mapping between the real
SQLite3 pragmas and the usage in the URI is more direct.

Use macros instead of case to detect pragmas from URI params and return
those as NamedTuple.

* Update README.md

---------

Co-authored-by: Brian J. Cardiff <bcardiff@gmail.com>
2023-02-18 17:06:09 -03:00
Brian J. Cardiff d15a8da05c
Release 0.19.0 (#79) 2022-01-28 15:33:18 -03:00
Brian J. Cardiff a9baaec310
Migrate CI to GitHub Actions (#78) 2022-01-28 15:14:33 -03:00
Brian J. Cardiff 2849fe15c8
Update to crystal-db ~> 0.11.0 (#77) 2022-01-27 11:09:27 -03:00
Ryan Westlund 985bfa2d7c
Fix timestamp reading issue (#68) 2021-02-28 14:18:08 -03:00
Brian J. Cardiff 367c11031d
Release 0.18.0 (#67) 2021-01-26 16:11:20 -03:00
Ryan Westlund 55b8399d7e
Enable REGEXP by connecting Crystal's stdlib Regex (#62) 2020-11-11 15:00:40 -03:00
Brian J. Cardiff 4abea0d326
Release 0.17.0 (#60) 2020-09-30 16:01:48 -03:00
Brian J. Cardiff d440f55b2f
CI: Add CircleCI using manastech/crystal@1.0 (#59) 2020-09-30 11:56:46 -03:00
Brian J. Cardiff 8586182fd8
Update crystal-db ~> 0.10.0 and Crystal 0.35.0 for logging support (#58)
* Update crystal-db for logging support

* Update sample

* Update to crystal-db ~> 0.10.0
2020-09-30 11:34:25 -03:00
Brian J. Cardiff 5f6055f4bd
Release 0.16.0 (#56) 2020-04-06 21:12:05 -03:00
Brian J. Cardiff ce0ca1c884
Update to crystal-db ~> 0.9.0 (#55) 2020-04-06 20:23:00 -03:00
Brian J. Cardiff d9f916a598
Update README.md for dynamic args (#53) 2020-01-14 10:29:58 -03:00
Brian J. Cardiff aed9acb225
Release 0.15.0 (#51) 2019-12-11 17:59:42 -03:00
Brian J. Cardiff 751bd5de97
Update to crystal-db ~> 0.8.0 (#50) 2019-12-11 17:48:03 -03:00
18 changed files with 387 additions and 33 deletions

61
.circleci/config.yml Normal file
View File

@ -0,0 +1,61 @@
version: 2.1
orbs:
crystal: manastech/crystal@1.0
commands:
install-sqlite:
steps:
- run:
name: Install `sqlite`
command: apt-get update && apt-get install -y libsqlite3-dev
jobs:
test:
parameters:
executor:
type: executor
default: crystal/default
executor: << parameters.executor >>
steps:
- install-sqlite
- crystal/version
- checkout
- crystal/with-shards-cache:
steps:
- crystal/shards-install
- crystal/spec
- crystal/format-check
executors:
nightly:
docker:
- image: 'crystallang/crystal:nightly'
environment:
SHARDS_OPTS: --ignore-crystal-version
workflows:
version: 2
build:
jobs:
- test
- test:
name: test-on-nightly
executor:
name: nightly
nightly:
triggers:
- schedule:
cron: "0 3 * * *"
filters:
branches:
only:
- master
jobs:
- test:
name: test-on-nightly
executor:
name: nightly

35
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: CI
on:
push:
pull_request:
branches: [master]
schedule:
- cron: '0 6 * * 1' # Every monday 6 AM
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
crystal: [1.0.0, latest, nightly]
runs-on: ${{ matrix.os }}
steps:
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
with:
crystal: ${{ matrix.crystal }}
- name: Download source
uses: actions/checkout@v2
- name: Install shards
run: shards install
- name: Run specs
run: crystal spec
- name: Check formatting
run: crystal tool format; git diff --exit-code
if: matrix.crystal == 'latest' && matrix.os == 'ubuntu-latest'

View File

@ -1,2 +0,0 @@
language: crystal
sudo: false

View File

@ -1,3 +1,39 @@
## v0.21.0 (2023-12-12)
* Update to crystal-db ~> 0.13.0. ([#94](https://github.com/crystal-lang/crystal-sqlite3/pull/94))
## v0.20.0 (2023-06-23)
* Update to crystal-db ~> 0.12.0. ([#91](https://github.com/crystal-lang/crystal-sqlite3/pull/91))
* Fix result set & connection release lifecycle. ([#90](https://github.com/crystal-lang/crystal-sqlite3/pull/90))
* Automatically set PRAGMAs using connection query params. ([#85](https://github.com/crystal-lang/crystal-sqlite3/pull/85), thanks @luislavena)
## v0.19.0 (2022-01-28)
* Update to crystal-db ~> 0.11.0. ([#77](https://github.com/crystal-lang/crystal-sqlite3/pull/77))
* Fix timestamps support to allow dealing with exact seconds values ([#68](https://github.com/crystal-lang/crystal-sqlite3/pull/68), thanks @yujiri8, @tenebrousedge)
* Migrate CI to GitHub Actions. ([#78](https://github.com/crystal-lang/crystal-sqlite3/pull/78))
This release requires Crystal 1.0.0 or later.
## v0.18.0 (2021-01-26)
* Add `REGEXP` support powered by Crystal's std-lib Regex. ([#62](https://github.com/crystal-lang/crystal-sqlite3/pull/62), thanks @yujiri8)
## v0.17.0 (2020-09-30)
* Update to crystal-db ~> 0.10.0. ([#58](https://github.com/crystal-lang/crystal-sqlite3/pull/58))
This release requires Crystal 0.35.0 or later.
## v0.16.0 (2020-04-06)
* Update to crystal-db ~> 0.9.0. ([#55](https://github.com/crystal-lang/crystal-sqlite3/pull/55))
## v0.15.0 (2019-12-11)
* Update to crystal-db ~> 0.8.0. ([#50](https://github.com/crystal-lang/crystal-sqlite3/pull/50))
## v0.14.0 (2019-09-23) ## v0.14.0 (2019-09-23)
* Update to crystal-db ~> 0.7.0. ([#44](https://github.com/crystal-lang/crystal-sqlite3/pull/44)) * Update to crystal-db ~> 0.7.0. ([#44](https://github.com/crystal-lang/crystal-sqlite3/pull/44))

View File

@ -26,7 +26,7 @@ DB.open "sqlite3://./data.db" do |db|
args = [] of DB::Any args = [] of DB::Any
args << "Sarah" args << "Sarah"
args << 33 args << 33
db.exec "insert into contacts values (?, ?)", args db.exec "insert into contacts values (?, ?)", args: args
puts "max age:" puts "max age:"
puts db.scalar "select max(age) from contacts" # => 33 puts db.scalar "select max(age) from contacts" # => 33
@ -46,5 +46,41 @@ end
### DB::Any ### DB::Any
* `Time` is implemented as `TEXT` column using `SQLite3::DATE_FORMAT` format. * `Time` is implemented as `TEXT` column using `SQLite3::DATE_FORMAT_SUBSECOND` format (or `SQLite3::DATE_FORMAT_SECOND` if the text does not contain a dot).
* `Bool` is implemented as `INT` column mapping `0`/`1` values. * `Bool` is implemented as `INT` column mapping `0`/`1` values.
### Setting PRAGMAs
You can adjust certain [SQLite3 PRAGMAs](https://www.sqlite.org/pragma.html)
automatically when the connection is created by using the query parameters:
```crystal
require "sqlite3"
DB.open "sqlite3://./data.db?journal_mode=wal&synchronous=normal" do |db|
# this database now uses WAL journal and normal synchronous mode
# (defaults were `delete` and `full`, respectively)
end
```
The following is the list of supported options:
| Name | Connection key |
|---------------------------|-----------------|
| [Busy Timeout][pragma-to] | `busy_timeout` |
| [Cache Size][pragma-cs] | `cache_size` |
| [Foreign Keys][pragma-fk] | `foreign_keys` |
| [Journal Mode][pragma-jm] | `journal_mode` |
| [Synchronous][pragma-sync] | `synchronous` |
| [WAL autocheckoint][pragma-walck] | `wal_autocheckpoint` |
Please note there values passed using these connection keys are passed
directly to SQLite3 without check or evaluation. Using incorrect values result
in no error by the library.
[pragma-to]: https://www.sqlite.org/pragma.html#pragma_busy_timeout
[pragma-cs]: https://www.sqlite.org/pragma.html#pragma_cache_size
[pragma-fk]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
[pragma-jm]: https://www.sqlite.org/pragma.html#pragma_journal_mode
[pragma-sync]: https://www.sqlite.org/pragma.html#pragma_synchronous
[pragma-walck]: https://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint

View File

@ -8,7 +8,7 @@ DB.open "sqlite3://%3Amemory%3A" do |db|
args = [] of DB::Any args = [] of DB::Any
args << "Sarah" args << "Sarah"
args << 33 args << 33
db.exec "insert into contacts values (?, ?)", args db.exec "insert into contacts values (?, ?)", args: args
puts "max age:" puts "max age:"
puts db.scalar "select max(age) from contacts" # => 33 puts db.scalar "select max(age) from contacts" # => 33

View File

@ -1,15 +1,15 @@
name: sqlite3 name: sqlite3
version: 0.14.0 version: 0.21.0
dependencies: dependencies:
db: db:
github: crystal-lang/crystal-db github: crystal-lang/crystal-db
version: ~> 0.7.0 version: ~> 0.13.0
authors: authors:
- Ary Borenszweig <aborenszweig@manas.tech> - Ary Borenszweig <aborenszweig@manas.tech>
- Brian J. Cardiff <bcardiff@manas.tech> - Brian J. Cardiff <bcardiff@gmail.com>
crystal: 0.28.0 crystal: ">= 1.0.0, < 2.0.0"
license: MIT license: MIT

View File

@ -10,6 +10,14 @@ private def dump(source, target)
end end
end end
private def it_sets_pragma_on_connection(pragma : String, value : String, expected, file = __FILE__, line = __LINE__)
it "sets pragma '#{pragma}' to #{expected}", file, line do
with_db("#{DB_FILENAME}?#{pragma}=#{value}") do |db|
db.scalar("PRAGMA #{pragma}").should eq(expected)
end
end
end
describe Connection do describe Connection do
it "opens a database and then backs it up to another db" do it "opens a database and then backs it up to another db" do
with_db do |db| with_db do |db|
@ -68,4 +76,34 @@ describe Connection do
cnn.scalar("select count(*) from person").should eq(1) cnn.scalar("select count(*) from person").should eq(1)
end end
end end
# adjust busy_timeout pragma (default is 0)
it_sets_pragma_on_connection "busy_timeout", "1000", 1000
# adjust cache_size pragma (default is -2000, 2MB)
it_sets_pragma_on_connection "cache_size", "-4000", -4000
# enable foreign_keys, no need to test off (is the default)
it_sets_pragma_on_connection "foreign_keys", "1", 1
it_sets_pragma_on_connection "foreign_keys", "yes", 1
it_sets_pragma_on_connection "foreign_keys", "true", 1
it_sets_pragma_on_connection "foreign_keys", "on", 1
# change journal_mode (default is delete)
it_sets_pragma_on_connection "journal_mode", "delete", "delete"
it_sets_pragma_on_connection "journal_mode", "truncate", "truncate"
it_sets_pragma_on_connection "journal_mode", "persist", "persist"
# change synchronous mode (default is 2, FULL)
it_sets_pragma_on_connection "synchronous", "0", 0
it_sets_pragma_on_connection "synchronous", "off", 0
it_sets_pragma_on_connection "synchronous", "1", 1
it_sets_pragma_on_connection "synchronous", "normal", 1
it_sets_pragma_on_connection "synchronous", "2", 2
it_sets_pragma_on_connection "synchronous", "full", 2
it_sets_pragma_on_connection "synchronous", "3", 3
it_sets_pragma_on_connection "synchronous", "extra", 3
# change wal_autocheckpoint (default is 1000)
it_sets_pragma_on_connection "wal_autocheckpoint", "0", 0
end end

View File

@ -13,7 +13,7 @@ private def cast_if_blob(expr, sql_type)
end end
end end
DB::DriverSpecs(DB::Any).run do DB::DriverSpecs(DB::Any).run do |ctx|
support_unprepared false support_unprepared false
before do before do
@ -34,7 +34,9 @@ DB::DriverSpecs(DB::Any).run do
sample_value 1.5_f32, "float", "1.5", type_safe_value: false sample_value 1.5_f32, "float", "1.5", type_safe_value: false
sample_value 1.5, "float", "1.5" sample_value 1.5, "float", "1.5"
sample_value Time.utc(2016, 2, 15), "text", "'2016-02-15 00:00:00.000'", type_safe_value: false sample_value Time.utc(2016, 2, 15), "text", "'2016-02-15 00:00:00.000'", type_safe_value: false
sample_value Time.utc(2016, 2, 15, 10, 15, 30), "text", "'2016-02-15 10:15:30'", type_safe_value: false
sample_value Time.utc(2016, 2, 15, 10, 15, 30), "text", "'2016-02-15 10:15:30.000'", type_safe_value: false sample_value Time.utc(2016, 2, 15, 10, 15, 30), "text", "'2016-02-15 10:15:30.000'", type_safe_value: false
sample_value Time.utc(2016, 2, 15, 10, 15, 30, nanosecond: 123000000), "text", "'2016-02-15 10:15:30.123'", type_safe_value: false
sample_value Time.local(2016, 2, 15, 7, 15, 30, location: Time::Location.fixed("fixed", -3*3600)), "text", "'2016-02-15 10:15:30.000'", type_safe_value: false sample_value Time.local(2016, 2, 15, 7, 15, 30, location: Time::Location.fixed("fixed", -3*3600)), "text", "'2016-02-15 10:15:30.000'", type_safe_value: false
ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65] ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65]
@ -102,7 +104,7 @@ DB::DriverSpecs(DB::Any).run do
db.exec %(insert into a (i, str) values (23, "bai bai");) db.exec %(insert into a (i, str) values (23, "bai bai");)
2.times do |i| 2.times do |i|
DB.open db.uri do |db| DB.open ctx.connection_string do |db|
begin begin
db.query("SELECT i, str FROM a WHERE i = ?", 23) do |rs| db.query("SELECT i, str FROM a WHERE i = ?", 23) do |rs|
rs.move_next rs.move_next
@ -128,4 +130,9 @@ DB::DriverSpecs(DB::Any).run do
it "handles multi-step pragma statements" do |db| it "handles multi-step pragma statements" do |db|
db.exec %(PRAGMA journal_mode = memory) db.exec %(PRAGMA journal_mode = memory)
end end
it "handles REGEXP operator" do |db|
(db.scalar "select 'unmatching text' REGEXP '^m'").should eq 0
(db.scalar "select 'matching text' REGEXP '^m'").should eq 1
end
end end

View File

@ -27,7 +27,7 @@ describe Driver do
it "should use database option as file to open" do it "should use database option as file to open" do
with_db do |db| with_db do |db|
db.driver.should be_a(SQLite3::Driver) db.checkout.should be_a(SQLite3::Connection)
File.exists?(DB_FILENAME).should be_true File.exists?(DB_FILENAME).should be_true
end end
end end

57
spec/result_set_spec.cr Normal file
View File

@ -0,0 +1,57 @@
require "./spec_helper"
describe SQLite3::ResultSet do
it "reads integer data types" do
with_db do |db|
db.exec "CREATE TABLE test_table (test_int integer)"
db.exec "INSERT INTO test_table (test_int) values (?)", 42
db.query("SELECT test_int FROM test_table") do |rs|
rs.each do
rs.read.should eq(42)
end
end
end
end
it "reads string data types" do
with_db do |db|
db.exec "CREATE TABLE test_table (test_text text)"
db.exec "INSERT INTO test_table (test_text) values (?), (?)", "abc", "123"
db.query("SELECT test_text FROM test_table") do |rs|
rs.each do
rs.read.should match(/abc|123/)
end
end
end
end
it "reads time data types" do
with_db do |db|
db.exec "CREATE TABLE test_table (test_date datetime)"
timestamp = Time.utc
db.exec "INSERT INTO test_table (test_date) values (current_timestamp)"
db.query("SELECT test_date FROM test_table") do |rs|
rs.each do
rs.read(Time).should be_close(timestamp, 1.second)
end
end
end
end
it "reads time stored in text fields, too" do
with_db do |db|
db.exec "CREATE TABLE test_table (test_date text)"
timestamp = Time.utc
# Try 3 different ways: our own two formats and using SQLite's current_timestamp.
# They should all work.
db.exec "INSERT INTO test_table (test_date) values (?)", timestamp.to_s SQLite3::DATE_FORMAT_SUBSECOND
db.exec "INSERT INTO test_table (test_date) values (?)", timestamp.to_s SQLite3::DATE_FORMAT_SECOND
db.exec "INSERT INTO test_table (test_date) values (current_timestamp)"
db.query("SELECT test_date FROM test_table") do |rs|
rs.each do
rs.read(Time).should be_close(timestamp, 1.second)
end
end
end
end
end

View File

@ -2,8 +2,18 @@ require "db"
require "./sqlite3/**" require "./sqlite3/**"
module SQLite3 module SQLite3
DATE_FORMAT = "%F %H:%M:%S.%L" DATE_FORMAT_SUBSECOND = "%F %H:%M:%S.%L"
DATE_FORMAT_SECOND = "%F %H:%M:%S"
# :nodoc: # :nodoc:
TIME_ZONE = Time::Location::UTC TIME_ZONE = Time::Location::UTC
# :nodoc:
REGEXP_FN = ->(context : LibSQLite3::SQLite3Context, argc : Int32, argv : LibSQLite3::SQLite3Value*) do
argv = Slice.new(argv, sizeof(Void*))
pattern = LibSQLite3.value_text(argv[0])
text = LibSQLite3.value_text(argv[1])
LibSQLite3.result_int(context, Regex.new(String.new(pattern)).matches?(String.new(text)).to_unsafe)
nil
end
end end

View File

@ -1,19 +1,62 @@
class SQLite3::Connection < DB::Connection class SQLite3::Connection < DB::Connection
def initialize(database) record Options,
super filename : String = ":memory:",
filename = self.class.filename(database.uri) # pragmas
# TODO maybe enable Flag::URI to parse query string in the uri as additional flags busy_timeout : String? = nil,
check LibSQLite3.open_v2(filename, out @db, (Flag::READWRITE | Flag::CREATE), nil) cache_size : String? = nil,
foreign_keys : String? = nil,
journal_mode : String? = nil,
synchronous : String? = nil,
wal_autocheckpoint : String? = nil do
def self.from_uri(uri : URI, default = Options.new)
params = HTTP::Params.parse(uri.query || "")
Options.new(
filename: URI.decode_www_form((uri.host || "") + uri.path),
# pragmas
busy_timeout: params.fetch("busy_timeout", default.busy_timeout),
cache_size: params.fetch("cache_size", default.cache_size),
foreign_keys: params.fetch("foreign_keys", default.foreign_keys),
journal_mode: params.fetch("journal_mode", default.journal_mode),
synchronous: params.fetch("synchronous", default.synchronous),
wal_autocheckpoint: params.fetch("wal_autocheckpoint", default.wal_autocheckpoint),
)
end
def pragma_statement
res = String.build do |str|
pragma_append(str, "busy_timeout", busy_timeout)
pragma_append(str, "cache_size", cache_size)
pragma_append(str, "foreign_keys", foreign_keys)
pragma_append(str, "journal_mode", journal_mode)
pragma_append(str, "synchronous", synchronous)
pragma_append(str, "wal_autocheckpoint", wal_autocheckpoint)
end
res.empty? ? nil : res
end
private def pragma_append(io, key, value)
return unless value
io << "PRAGMA #{key}=#{value};"
end
end
def initialize(options : ::DB::Connection::Options, sqlite3_options : Options)
super(options)
check LibSQLite3.open_v2(sqlite3_options.filename, out @db, (Flag::READWRITE | Flag::CREATE), nil)
# 2 means 2 arguments; 1 is the code for UTF-8
check LibSQLite3.create_function(@db, "regexp", 2, 1, nil, SQLite3::REGEXP_FN, nil, nil)
if pragma_statement = sqlite3_options.pragma_statement
check LibSQLite3.exec(@db, pragma_statement, nil, nil, nil)
end
rescue rescue
raise DB::ConnectionRefused.new raise DB::ConnectionRefused.new
end end
def self.filename(uri : URI) def self.filename(uri : URI)
{% if compare_versions(Crystal::VERSION, "0.30.0-0") >= 0 %} URI.decode_www_form((uri.host || "") + uri.path)
URI.decode_www_form((uri.host || "") + uri.path)
{% else %}
URI.unescape((uri.host || "") + uri.path)
{% end %}
end end
def build_prepared_statement(query) : Statement def build_prepared_statement(query) : Statement

View File

@ -1,6 +1,16 @@
class SQLite3::Driver < DB::Driver class SQLite3::Driver < DB::Driver
def build_connection(context : DB::ConnectionContext) : SQLite3::Connection class ConnectionBuilder < ::DB::ConnectionBuilder
SQLite3::Connection.new(context) def initialize(@options : ::DB::Connection::Options, @sqlite3_options : SQLite3::Connection::Options)
end
def build : ::DB::Connection
SQLite3::Connection.new(@options, @sqlite3_options)
end
end
def connection_builder(uri : URI) : ::DB::ConnectionBuilder
params = HTTP::Params.parse(uri.query || "")
ConnectionBuilder.new(connection_options(params), SQLite3::Connection::Options.from_uri(uri))
end end
end end

View File

@ -5,6 +5,8 @@ lib LibSQLite3
type SQLite3 = Void* type SQLite3 = Void*
type Statement = Void* type Statement = Void*
type SQLite3Backup = Void* type SQLite3Backup = Void*
type SQLite3Context = Void*
type SQLite3Value = Void*
enum Code enum Code
# Successful result # Successful result
@ -72,6 +74,7 @@ lib LibSQLite3
end end
alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32 alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32
alias FuncCallback = (SQLite3Context, Int32, SQLite3Value*) -> Void
fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags : ::SQLite3::Flag, zVfs : UInt8*) : Int32 fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags : ::SQLite3::Flag, zVfs : UInt8*) : Int32
@ -83,6 +86,7 @@ lib LibSQLite3
fun backup_finish = sqlite3_backup_finish(SQLite3Backup) : 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 exec = sqlite3_exec(db : SQLite3, zSql : UInt8*, pCallback : Callback, pCallbackArgs : Void*, pzErrMsg : 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
fun column_type = sqlite3_column_type(stmt : Statement, iCol : Int32) : ::SQLite3::Type fun column_type = sqlite3_column_type(stmt : Statement, iCol : Int32) : ::SQLite3::Type
@ -108,4 +112,8 @@ lib LibSQLite3
fun finalize = sqlite3_finalize(stmt : Statement) : Int32 fun finalize = sqlite3_finalize(stmt : Statement) : Int32
fun close_v2 = sqlite3_close_v2(SQLite3) : Int32 fun close_v2 = sqlite3_close_v2(SQLite3) : Int32
fun close = sqlite3_close(SQLite3) : Int32 fun close = sqlite3_close(SQLite3) : Int32
fun create_function = sqlite3_create_function(SQLite3, funcName : UInt8*, nArg : Int32, eTextRep : Int32, pApp : Void*, xFunc : FuncCallback, xStep : Void*, xFinal : Void*) : Int32
fun value_text = sqlite3_value_text(SQLite3Value) : UInt8*
fun result_int = sqlite3_result_int(SQLite3Context, Int32) : Nil
end end

View File

@ -2,8 +2,8 @@ class SQLite3::ResultSet < DB::ResultSet
@column_index = 0 @column_index = 0
protected def do_close protected def do_close
super
LibSQLite3.reset(self) LibSQLite3.reset(self)
super
end end
# Advances to the next row. Returns `true` if there's a next row, # Advances to the next row. Returns `true` if there's a next row,
@ -47,6 +47,10 @@ class SQLite3::ResultSet < DB::ResultSet
value value
end end
def next_column_index : Int32
@column_index
end
def read(t : Int32.class) : Int32 def read(t : Int32.class) : Int32
read(Int64).to_i32 read(Int64).to_i32
end end
@ -64,11 +68,22 @@ class SQLite3::ResultSet < DB::ResultSet
end end
def read(t : Time.class) : Time def read(t : Time.class) : Time
Time.parse read(String), SQLite3::DATE_FORMAT, location: SQLite3::TIME_ZONE text = read(String)
if text.includes? "."
Time.parse text, SQLite3::DATE_FORMAT_SUBSECOND, location: SQLite3::TIME_ZONE
else
Time.parse text, SQLite3::DATE_FORMAT_SECOND, location: SQLite3::TIME_ZONE
end
end end
def read(t : Time?.class) : Time? def read(t : Time?.class) : Time?
read(String?).try { |v| Time.parse(v, SQLite3::DATE_FORMAT, location: SQLite3::TIME_ZONE) } read(String?).try { |v|
if v.includes? "."
Time.parse v, SQLite3::DATE_FORMAT_SUBSECOND, location: SQLite3::TIME_ZONE
else
Time.parse v, SQLite3::DATE_FORMAT_SECOND, location: SQLite3::TIME_ZONE
end
}
end end
def read(t : Bool.class) : Bool def read(t : Bool.class) : Bool

View File

@ -1,7 +1,7 @@
class SQLite3::Statement < DB::Statement class SQLite3::Statement < DB::Statement
def initialize(connection, sql) def initialize(connection, command)
super(connection) super(connection, command)
check LibSQLite3.prepare_v2(sqlite3_connection, sql, sql.bytesize + 1, out @stmt, nil) check LibSQLite3.prepare_v2(sqlite3_connection, command, command.bytesize + 1, out @stmt, nil)
end end
protected def perform_query(args : Enumerable) : DB::ResultSet protected def perform_query(args : Enumerable) : DB::ResultSet
@ -70,7 +70,7 @@ class SQLite3::Statement < DB::Statement
end end
private def bind_arg(index, value : Time) private def bind_arg(index, value : Time)
bind_arg(index, value.in(SQLite3::TIME_ZONE).to_s(SQLite3::DATE_FORMAT)) bind_arg(index, value.in(SQLite3::TIME_ZONE).to_s(SQLite3::DATE_FORMAT_SUBSECOND))
end end
private def bind_arg(index, value) private def bind_arg(index, value)

View File

@ -1,3 +1,3 @@
module SQLite3 module SQLite3
VERSION = "0.14.0" VERSION = "0.21.0"
end end