Full Code of crystal-lang/crystal-sqlite3 for AI

master 271a1314a6c6 cached
25 files
46.1 KB
14.1k tokens
1 requests
Download .txt
Repository: crystal-lang/crystal-sqlite3
Branch: master
Commit: 271a1314a6c6
Files: 25
Total size: 46.1 KB

Directory structure:
gitextract_6uvl4mnq/

├── .circleci/
│   └── config.yml
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── compile_and_link_sqlite.md
├── samples/
│   └── memory.cr
├── shard.yml
├── spec/
│   ├── connection_spec.cr
│   ├── db_spec.cr
│   ├── driver_spec.cr
│   ├── pool_spec.cr
│   ├── result_set_spec.cr
│   └── spec_helper.cr
└── src/
    ├── sqlite3/
    │   ├── connection.cr
    │   ├── driver.cr
    │   ├── exception.cr
    │   ├── flags.cr
    │   ├── lib_sqlite3.cr
    │   ├── result_set.cr
    │   ├── statement.cr
    │   ├── type.cr
    │   └── version.cr
    └── sqlite3.cr

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
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



================================================
FILE: .github/workflows/ci.yml
================================================
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'


================================================
FILE: .gitignore
================================================
/doc/
/lib/
/.crystal/
/.shards/


# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock



================================================
FILE: CHANGELOG.md
================================================
## v0.23.0 (2026-04-12)

* Use `URI#hostname` to avoid interpreting `:memory:` as `[:memory:]` ([#105](https://github.com/crystal-lang/crystal-sqlite3/pull/105), thanks @bcardiff)
* Prevent arguments from being GC'd before statement is executed ([#111](https://github.com/crystal-lang/crystal-sqlite3/pull/111), thanks @lwakefield)
* Add DLL name to `@[Link]` annotation ([#107](https://github.com/crystal-lang/crystal-sqlite3/pull/107), thanks @HertzDevil)
* Replace Travis CI badge with GitHub Action ([#104](https://github.com/crystal-lang/crystal-sqlite3/pull/104), thanks @bcardiff)
* Add another readme example ([#112](https://github.com/crystal-lang/crystal-sqlite3/pull/112), thanks @bcardiff)

## v0.22.0 (2025-09-05)

* Add arg support for `Int16`, `Int8`, `UInt16`, `UInt8`. ([#98](https://github.com/crystal-lang/crystal-sqlite3/pull/98), [#99](https://github.com/crystal-lang/crystal-sqlite3/pull/99), thanks @baseballlover723, @bcardiff)
* Add docs on how to compile and link SQLite ([#74](https://github.com/crystal-lang/crystal-sqlite3/pull/74), thanks @chillfox)
* Update to crystal-db ~> 0.14.0. ([#102](https://github.com/crystal-lang/crystal-sqlite3/pull/102), thanks @bcardiff)
* Update formatting ([#100](https://github.com/crystal-lang/crystal-sqlite3/pull/100), thanks @straight-shoota)

## 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)

* Update to crystal-db ~> 0.7.0. ([#44](https://github.com/crystal-lang/crystal-sqlite3/pull/44))

## v0.13.0 (2019-08-02)

* Fix compatibility issues for Crystal 0.30.0. ([#43](https://github.com/crystal-lang/crystal-sqlite3/pull/43))

## v0.12.0 (2019-06-07)

This release requires crystal >= 0.28.0

* Fix compatibility issues for crystal 0.29.0 ([#40](https://github.com/crystal-lang/crystal-sqlite3/pull/40))

## v0.11.0 (2019-04-18)

* Fix compatibility issues for crystal 0.28.0 ([#38](https://github.com/crystal-lang/crystal-sqlite3/pull/38))
* Add complete list of `LibSQLite3::Code` values. ([#36](https://github.com/crystal-lang/crystal-sqlite3/pull/36), thanks @t-richards)

## v0.10.0 (2018-06-18)

* Fix compatibility issues for crystal 0.25.0 ([#34](https://github.com/crystal-lang/crystal-sqlite3/pull/34))
  * All the time instances are translated to UTC before saving them in the db

## v0.9.0 (2017-12-31)

* Update to crystal-db ~> 0.5.0

## v0.8.3 (2017-11-07)

* Update to crystal-db ~> 0.4.1
* Add `SQLite3::VERSION` constant with shard version.
* Add support for multi-steps statements execution. (see [#27](https://github.com/crystal-lang/crystal-sqlite3/pull/27), thanks @t-richards)
* Fix how resources are released. (see [#23](https://github.com/crystal-lang/crystal-sqlite3/pull/23), thanks @benoist)
* Fix blob c bindings. (see [#28](https://github.com/crystal-lang/crystal-sqlite3/pull/28), thanks @rufusroflpunch)

## v0.8.2 (2017-03-21)


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 Brian J. Cardiff

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
[![CI](https://github.com/crystal-lang/crystal-sqlite3/actions/workflows/ci.yml/badge.svg)](https://github.com/crystal-lang/crystal-sqlite3/actions/workflows/ci.yml)

# crystal-sqlite3

SQLite3 bindings for [Crystal](http://crystal-lang.org/).

Check [crystal-db](https://github.com/crystal-lang/crystal-db) for general db driver documentation. crystal-sqlite3 driver is registered under `sqlite3://` uri.

## Installation

Add this to your application's `shard.yml`:

```yml
dependencies:
  sqlite3:
    github: crystal-lang/crystal-sqlite3
```

### Usage

```crystal
require "sqlite3"

DB.open "sqlite3://./data.db" do |db|
  db.exec "create table contacts (name text, age integer)"
  db.exec "insert into contacts values (?, ?)", "John Doe", 30

  args = [] of DB::Any
  args << "Sarah"
  args << 33
  db.exec "insert into contacts values (?, ?)", args: args

  puts "max age:"
  puts db.scalar "select max(age) from contacts" # => 33

  puts "contacts:"
  db.query "select name, age from contacts order by age desc" do |rs|
    puts "#{rs.column_name(0)} (#{rs.column_name(1)})"
    # => name (age)
    rs.each do
      puts "#{rs.read(String)} (#{rs.read(Int32)})"
      # => Sarah (33)
      # => John Doe (30)
    end
  end

  contacts = db.query_all "SELECT name, age FROM contacts", as: {name: String, age: Int64}
  puts contacts # => [{name: "John Doe", age: 30}, {name: "Sarah", age: 33}]
end
```

### DB::Any

* `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.

### 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

## Guides

- [Compile and link SQLite](compile_and_link_sqlite.md)


================================================
FILE: compile_and_link_sqlite.md
================================================
# How to Compile And Link SQLite

There are two main reasons to compile SQLite from source and they are both about getting features that are otherwise unavailable.

- You may need a feature from a release that haven't made it to your distro yet or you want to use the latest code from development.
- Perhaps you want some compile time features enabled that are not commonly enabled by default.

This guide assumes the first reason and goes through how to compile the latest release.


## Install Prerequisites (Ubuntu)

On Ubuntu you will need build-essential installed at a minimum.

```sh
sudo apt update
sudo apt install build-essential
```


## Download And Extract The Source Code

Source code for the latest release can be downloaded from the [SQLite Download Page](https://sqlite.org/download.html).
Look for "C source code as an amalgamation", It should be the first one on the page.

```sh
wget https://sqlite.org/2021/sqlite-amalgamation-3370000.zip
unzip sqlite-amalgamation-3370000.zip
cd sqlite-amalgamation-3370000
```


## Compile SQLite

Compile the sqlite command.

```sh
gcc shell.c sqlite3.c -lpthread -ldl -o sqlite3
./sqlite3 --version
```

Compile libsqlite.

```sh
gcc -lpthread -ldl -shared -o libsqlite3.so.0 -fPIC sqlite3.c
```

## Using The New Version of SQLite

The path to libsqlite can be specified at runtime with "LD_LIBRARY_PATH".

```sh
# directory of your crystal app
cd ../app

# Crystal run
LD_LIBRARY_PATH=../sqlite-amalgamation-3370000 crystal run src/app.cr

# This way will allow specifying the library location at runtime if it is different from the system default.
crystal build --release --link-flags -L"$(realpath ../sqlite-amalgamation-3370000/libsqlite3.so.0)" src/app.cr
LD_LIBRARY_PATH=../sqlite-amalgamation-3370000 ./app

# ldd can be used to see which libsqlite is being linked
LD_LIBRARY_PATH=../sqlite-amalgamation-3370000 ldd ./app
```

Or the absolute path to libsqlite can be specified at compile time.

```sh
crystal run --link-flags "$(realpath ../sqlite-amalgamation-3370000/libsqlite3.so.0)" src/app.cr

# This will create a version that only works if libsqlite in the excact same location as when it was compiled.
crystal build --release --link-flags "$(realpath ../sqlite-amalgamation-3370000/libsqlite3.so.0)" src/app.cr
./app

# Use ldd to see which libsqlite is being linked
ldd ./app
```


## Check SQLite Version From Crystal

To check which version of SQLite is being used from Crystal.

```crystal
# src/app.cr

DB_URI = "sqlite3://:memory:"

DB.open DB_URI do |db|
    db_version = db.scalar "select sqlite_version();"
    puts "SQLite #{db_version}"
end
```


================================================
FILE: samples/memory.cr
================================================
require "db"
require "../src/sqlite3"

DB.open "sqlite3://%3Amemory%3A" do |db|
  db.exec "create table contacts (name text, age integer)"
  db.exec "insert into contacts values (?, ?)", "John Doe", 30

  args = [] of DB::Any
  args << "Sarah"
  args << 33
  db.exec "insert into contacts values (?, ?)", args: args

  puts "max age:"
  puts db.scalar "select max(age) from contacts" # => 33

  puts "contacts:"
  db.query "select name, age from contacts order by age desc" do |rs|
    puts "#{rs.column_name(0)} (#{rs.column_name(1)})"
    # => name (age)
    rs.each do
      puts "#{rs.read(String)} (#{rs.read(Int32)})"
      # => Sarah (33)
      # => John Doe (30)
    end
  end
end


================================================
FILE: shard.yml
================================================
name: sqlite3
version: 0.23.0

dependencies:
  db:
    github: crystal-lang/crystal-db
    version: ~> 0.14.0

authors:
  - Ary Borenszweig <aborenszweig@manas.tech>
  - Brian J. Cardiff <bcardiff@gmail.com>

crystal: ">= 1.0.0, < 2.0.0"

license: MIT


================================================
FILE: spec/connection_spec.cr
================================================
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

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
  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 text, 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 text, 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

  it "opens a database, inserts records (>1024K), and dumps to an in-memory db" do
    with_db do |db|
      with_mem_db do |in_memory_db|
        db.exec "create table person (name text, age integer)"
        db.transaction do |tx|
          100_000.times { tx.connection.exec "insert into person values (\"foo\", 10)" }
        end
        dump db, in_memory_db
        in_memory_db.scalar("select count(*) from person").should eq(100_000)
      end
    end
  end

  it "opens a connection without the pool" do
    with_cnn do |cnn|
      cnn.should be_a(SQLite3::Connection)

      cnn.exec "create table person (name text, age integer)"
      cnn.exec "insert into person values (\"foo\", 10)"

      cnn.scalar("select count(*) from person").should eq(1)
    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


================================================
FILE: spec/db_spec.cr
================================================
require "./spec_helper"
require "db/spec"

private class NotSupportedType
end

private def cast_if_blob(expr, sql_type)
  case sql_type
  when "blob"
    "cast(#{expr} as blob)"
  else
    expr
  end
end

DB::DriverSpecs(SQLite3::Any).run do |ctx|
  support_unprepared false

  before do
    File.delete(DB_FILENAME) if File.exists?(DB_FILENAME)
  end
  after do
    File.delete(DB_FILENAME) if File.exists?(DB_FILENAME)
  end

  connection_string "sqlite3:#{DB_FILENAME}"
  # ? can use many ... (:memory:)

  sample_value true, "int", "1", type_safe_value: false
  sample_value false, "int", "0", type_safe_value: false
  sample_value 2, "int", "2", type_safe_value: false
  sample_value 1_i64, "int", "1"
  sample_value "hello", "text", "'hello'"
  sample_value 1.5_f32, "float", "1.5", type_safe_value: false
  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, 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, 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 Int8::MIN, "int", Int8::MIN.to_s, type_safe_value: false
  sample_value Int8::MAX, "int", Int8::MAX.to_s, type_safe_value: false
  sample_value UInt8::MIN, "int", UInt8::MIN.to_s, type_safe_value: false
  sample_value UInt8::MAX, "int", UInt8::MAX.to_s, type_safe_value: false
  sample_value Int16::MIN, "int", Int16::MIN.to_s, type_safe_value: false
  sample_value Int16::MAX, "int", Int16::MAX.to_s, type_safe_value: false
  sample_value UInt16::MIN, "int", UInt16::MIN.to_s, type_safe_value: false
  sample_value UInt16::MAX, "int", UInt16::MAX.to_s, type_safe_value: false
  sample_value Int32::MIN, "int", Int32::MIN.to_s, type_safe_value: false
  sample_value Int32::MAX, "int", Int32::MAX.to_s, type_safe_value: false
  sample_value UInt32::MIN, "int", UInt32::MIN.to_s, type_safe_value: false
  sample_value UInt32::MAX, "int", UInt32::MAX.to_s, type_safe_value: false
  sample_value Int64::MIN, "int", Int64::MIN.to_s, type_safe_value: false
  sample_value Int64::MAX, "int", Int64::MAX.to_s, type_safe_value: false

  ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65]
  sample_value Bytes.new(ary.to_unsafe, ary.size), "blob", "X'53514C697465'" # , type_safe_value: false

  binding_syntax do |index|
    "?"
  end

  create_table_1column_syntax do |table_name, col1|
    "create table #{table_name} (#{col1.name} #{col1.sql_type} #{col1.null ? "NULL" : "NOT NULL"})"
  end

  create_table_2columns_syntax do |table_name, col1, col2|
    "create table #{table_name} (#{col1.name} #{col1.sql_type} #{col1.null ? "NULL" : "NOT NULL"}, #{col2.name} #{col2.sql_type} #{col2.null ? "NULL" : "NOT NULL"})"
  end

  select_1column_syntax do |table_name, col1|
    "select #{cast_if_blob(col1.name, col1.sql_type)} from #{table_name}"
  end

  select_2columns_syntax do |table_name, col1, col2|
    "select #{cast_if_blob(col1.name, col1.sql_type)}, #{cast_if_blob(col2.name, col2.sql_type)} from #{table_name}"
  end

  select_count_syntax do |table_name|
    "select count(*) from #{table_name}"
  end

  select_scalar_syntax do |expression, sql_type|
    "select #{cast_if_blob(expression, sql_type)}"
  end

  insert_1column_syntax do |table_name, col, expression|
    "insert into #{table_name} (#{col.name}) values (#{expression})"
  end

  insert_2columns_syntax do |table_name, col1, expr1, col2, expr2|
    "insert into #{table_name} (#{col1.name}, #{col2.name}) values (#{expr1}, #{expr2})"
  end

  drop_table_if_exists_syntax do |table_name|
    "drop table if exists #{table_name}"
  end

  it "gets last insert row id", prepared: :both do |db|
    db.exec "create table person (name text, age integer)"
    db.exec %(insert into person values ("foo", 10))
    res = db.exec %(insert into person values ("foo", 10))
    res.last_insert_id.should eq(2)
    res.rows_affected.should eq(1)
  end

  # TODO timestamp support

  it "raises on unsupported param types" do |db|
    expect_raises Exception, "SQLite3::Statement does not support NotSupportedType params" do
      db.query "select ?", NotSupportedType.new
    end
    # TODO raising exception does not close the connection and pool is exhausted
  end

  it "ensures statements are closed" do |db|
    db.exec %(create table if not exists a (i int not null, str text not null);)
    db.exec %(insert into a (i, str) values (23, "bai bai");)

    2.times do |i|
      DB.open ctx.connection_string do |db|
        begin
          db.query("SELECT i, str FROM a WHERE i = ?", 23) do |rs|
            rs.move_next
            break
          end
        rescue e : SQLite3::Exception
          fail("Expected no exception, but got \"#{e.message}\"")
        end

        begin
          db.exec("UPDATE a SET i = ? WHERE i = ?", 23, 23)
        rescue e : SQLite3::Exception
          fail("Expected no exception, but got \"#{e.message}\"")
        end
      end
    end
  end

  it "handles single-step pragma statements" do |db|
    db.exec %(PRAGMA synchronous = OFF)
  end

  it "handles multi-step pragma statements" do |db|
    db.exec %(PRAGMA journal_mode = memory)
  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


================================================
FILE: spec/driver_spec.cr
================================================
require "./spec_helper"

def assert_filename(uri, filename)
  SQLite3::Connection.filename(URI.parse(uri)).should eq(filename)
end

describe Driver do
  it "should register sqlite3 name" do
    DB.driver_class("sqlite3").should eq(SQLite3::Driver)
  end

  it "should get filename from uri" do
    assert_filename("sqlite3:%3Amemory%3A", ":memory:")
    assert_filename("sqlite3://%3Amemory%3A", ":memory:")

    assert_filename("sqlite3:./file.db", "./file.db")
    assert_filename("sqlite3://./file.db", "./file.db")

    assert_filename("sqlite3:/path/to/file.db", "/path/to/file.db")
    assert_filename("sqlite3:///path/to/file.db", "/path/to/file.db")

    assert_filename("sqlite3:./file.db?max_pool_size=5", "./file.db")
    assert_filename("sqlite3:/path/to/file.db?max_pool_size=5", "/path/to/file.db")
    assert_filename("sqlite3://./file.db?max_pool_size=5", "./file.db")
    assert_filename("sqlite3:///path/to/file.db?max_pool_size=5", "/path/to/file.db")
  end

  it "should use database option as file to open" do
    with_db do |db|
      db.checkout.should be_a(SQLite3::Connection)
      File.exists?(DB_FILENAME).should be_true
    end
  end
end


================================================
FILE: spec/pool_spec.cr
================================================
require "./spec_helper"

describe DB::Pool do
  it "should write from multiple connections" do
    channel = Channel(Nil).new
    fibers = 5
    max_n = 50
    with_db "#{DB_FILENAME}?max_pool_size=#{fibers}" do |db|
      db.exec "create table numbers (n integer, fiber integer)"

      fibers.times do |f|
        spawn do
          (1..max_n).each do |n|
            db.exec "insert into numbers (n, fiber) values (?, ?)", n, f
            sleep 0.01
          end
          channel.send nil
        end
      end

      fibers.times { channel.receive }

      # all numbers were inserted
      s = fibers * max_n * (max_n + 1) // 2
      db.scalar("select sum(n) from numbers").should eq(s)

      # numbers were not inserted one fiber at a time
      rows = db.query_all "select n, fiber from numbers", as: {Int32, Int32}
      rows.map(&.[1]).should_not eq(rows.map(&.[1]).sort)
    end
  end
end


================================================
FILE: spec/result_set_spec.cr
================================================
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


================================================
FILE: spec/spec_helper.cr
================================================
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_cnn(&block : DB::Connection ->)
  File.delete(DB_FILENAME) rescue nil
  DB.connect "sqlite3:#{DB_FILENAME}", &block
ensure
  File.delete(DB_FILENAME)
end

def with_db(config, &block : DB::Database ->)
  uri = "sqlite3:#{config}"
  filename = SQLite3::Connection.filename(URI.parse(uri))
  File.delete(filename) rescue nil
  DB.open uri, &block
ensure
  File.delete(filename) if filename
end

def with_mem_db(&block : DB::Database ->)
  DB.open "sqlite3://%3Amemory%3A", &block
end


================================================
FILE: src/sqlite3/connection.cr
================================================
class SQLite3::Connection < DB::Connection
  record Options,
    filename : String = ":memory:",
    # pragmas
    busy_timeout : String? = 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.hostname || "") + 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
    raise DB::ConnectionRefused.new
  end

  def self.filename(uri : URI)
    URI.decode_www_form((uri.hostname || "") + uri.path)
  end

  def build_prepared_statement(query) : Statement
    Statement.new(self, query)
  end

  def build_unprepared_statement(query) : Statement
    # sqlite3 does not support unprepared statement.
    # All statements once prepared should be released
    # when unneeded. Unprepared statement are not aim
    # to leave state in the connection. Mimicking them
    # with prepared statement would be wrong with
    # respect connection resources.
    raise DB::Error.new("SQLite3 driver does not support unprepared statements")
  end

  def do_close
    super
    check LibSQLite3.close(self)
  end

  # :nodoc:
  def perform_begin_transaction
    self.prepared.exec "BEGIN"
  end

  # :nodoc:
  def perform_commit_transaction
    self.prepared.exec "COMMIT"
  end

  # :nodoc:
  def perform_rollback_transaction
    self.prepared.exec "ROLLBACK"
  end

  # :nodoc:
  def perform_create_savepoint(name)
    self.prepared.exec "SAVEPOINT #{name}"
  end

  # :nodoc:
  def perform_release_savepoint(name)
    self.prepared.exec "RELEASE SAVEPOINT #{name}"
  end

  # :nodoc:
  def perform_rollback_savepoint(name)
    self.prepared.exec "ROLLBACK TO #{name}"
  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

  private def check(code)
    raise Exception.new(self) unless code == 0
  end
end


================================================
FILE: src/sqlite3/driver.cr
================================================
class SQLite3::Driver < DB::Driver
  class ConnectionBuilder < ::DB::ConnectionBuilder
    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

DB.register_driver "sqlite3", SQLite3::Driver


================================================
FILE: src/sqlite3/exception.cr
================================================
# Exception thrown on invalid SQLite3 operations.
class SQLite3::Exception < ::Exception
  # The internal code associated with the failure.
  getter code

  def initialize(db)
    super(String.new(LibSQLite3.errmsg(db)))
    @code = LibSQLite3.errcode(db)
  end
end


================================================
FILE: src/sqlite3/flags.cr
================================================
@[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


================================================
FILE: src/sqlite3/lib_sqlite3.cr
================================================
require "./type"

@[Link("sqlite3")]
{% if flag?(:msvc) %}
  @[Link(dll: "sqlite3.dll")]
{% end %}
lib LibSQLite3
  type SQLite3 = Void*
  type Statement = Void*
  type SQLite3Backup = Void*
  type SQLite3Context = Void*
  type SQLite3Value = Void*

  enum Code
    # Successful result
    OKAY = 0
    # Generic error
    ERROR = 1
    # Internal logic error in SQLite
    INTERNAL = 2
    # Access permission denied
    PERM = 3
    # Callback routine requested an abort
    ABORT = 4
    # The database file is locked
    BUSY = 5
    # A table in the database is locked
    LOCKED = 6
    # A malloc() failed
    NOMEM = 7
    # Attempt to write a readonly database
    READONLY = 8
    # Operation terminated by sqlite3_interrupt()
    INTERRUPT = 9
    # Some kind of disk I/O error occurred
    IOERR = 10
    # The database disk image is malformed
    CORRUPT = 11
    # Unknown opcode in sqlite3_file_control()
    NOTFOUND = 12
    # Insertion failed because database is full
    FULL = 13
    # Unable to open the database file
    CANTOPEN = 14
    # Database lock protocol error
    PROTOCOL = 15
    # Internal use only
    EMPTY = 16
    # The database schema changed
    SCHEMA = 17
    # String or BLOB exceeds size limit
    TOOBIG = 18
    # Abort due to constraint violation
    CONSTRAINT = 19
    # Data type mismatch
    MISMATCH = 20
    # Library used incorrectly
    MISUSE = 21
    # Uses OS features not supported on host
    NOLFS = 22
    # Authorization denied
    AUTH = 23
    # Not used
    FORMAT = 24
    # 2nd parameter to sqlite3_bind out of range
    RANGE = 25
    # File opened that is not a database file
    NOTADB = 26
    # Notifications from sqlite3_log()
    NOTICE = 27
    # Warnings from sqlite3_log()
    WARNING = 28
    # sqlite3_step() has another row ready
    ROW = 100
    # sqlite3_step() has finished executing
    DONE = 101
  end

  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 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, Int32) : 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 exec = sqlite3_exec(db : SQLite3, zSql : UInt8*, pCallback : Callback, pCallbackArgs : Void*, pzErrMsg : UInt8**) : Int32
  fun step = sqlite3_step(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_int64 = sqlite3_column_int64(stmt : Statement, iCol : Int32) : Int64
  fun column_double = sqlite3_column_double(stmt : Statement, iCol : Int32) : Float64
  fun column_text = sqlite3_column_text(stmt : Statement, iCol : Int32) : UInt8*
  fun column_bytes = sqlite3_column_bytes(stmt : Statement, iCol : Int32) : Int32
  fun column_blob = sqlite3_column_blob(stmt : Statement, iCol : Int32) : UInt8*

  fun bind_int = sqlite3_bind_int(stmt : Statement, idx : Int32, value : Int32) : Int32
  fun bind_int64 = sqlite3_bind_int64(stmt : Statement, idx : Int32, value : Int64) : Int32
  fun bind_text = sqlite3_bind_text(stmt : Statement, idx : Int32, value : UInt8*, bytes : Int32, destructor : Void* ->) : Int32
  fun bind_blob = sqlite3_bind_blob(stmt : Statement, idx : Int32, value : UInt8*, bytes : Int32, destructor : Void* ->) : Int32
  fun bind_null = sqlite3_bind_null(stmt : Statement, idx : Int32) : Int32
  fun bind_double = sqlite3_bind_double(stmt : Statement, idx : Int32, value : Float64) : Int32

  fun bind_parameter_index = sqlite3_bind_parameter_index(stmt : Statement, name : UInt8*) : Int32
  fun reset = sqlite3_reset(stmt : Statement) : Int32
  fun column_name = sqlite3_column_name(stmt : Statement, idx : Int32) : UInt8*
  fun last_insert_rowid = sqlite3_last_insert_rowid(db : SQLite3) : Int64
  fun changes = sqlite3_changes(db : SQLite3) : Int32

  fun finalize = sqlite3_finalize(stmt : Statement) : Int32
  fun close_v2 = sqlite3_close_v2(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


================================================
FILE: src/sqlite3/result_set.cr
================================================
class SQLite3::ResultSet < DB::ResultSet
  @column_index = 0

  protected def do_close
    LibSQLite3.reset(self)
    super
  end

  # Advances to the next row. Returns `true` if there's a next row,
  # `false` otherwise. Must be called at least once to advance to the first
  # row.
  def move_next : Bool
    @column_index = 0

    case step
    when LibSQLite3::Code::ROW
      true
    when LibSQLite3::Code::DONE
      false
    else
      raise Exception.new(sqlite3_statement.sqlite3_connection)
    end
  end

  def read
    col = @column_index
    value =
      case LibSQLite3.column_type(self, col)
      when Type::INTEGER
        LibSQLite3.column_int64(self, col)
      when Type::FLOAT
        LibSQLite3.column_double(self, col)
      when Type::BLOB
        blob = LibSQLite3.column_blob(self, col)
        bytes = LibSQLite3.column_bytes(self, col)
        ptr = Pointer(UInt8).malloc(bytes)
        ptr.copy_from(blob, bytes)
        Bytes.new(ptr, bytes)
      when Type::TEXT
        String.new(LibSQLite3.column_text(self, col))
      when Type::NULL
        nil
      else
        raise Exception.new(sqlite3_statement.sqlite3_connection)
      end
    @column_index += 1
    value
  end

  def next_column_index : Int32
    @column_index
  end

  def read(t : UInt8.class) : UInt8
    read(Int64).to_u8
  end

  def read(type : UInt8?.class) : UInt8?
    read(Int64?).try &.to_u8
  end

  def read(t : UInt16.class) : UInt16
    read(Int64).to_u16
  end

  def read(type : UInt16?.class) : UInt16?
    read(Int64?).try &.to_u16
  end

  def read(t : UInt32.class) : UInt32
    read(Int64).to_u32
  end

  def read(type : UInt32?.class) : UInt32?
    read(Int64?).try &.to_u32
  end

  def read(t : Int8.class) : Int8
    read(Int64).to_i8
  end

  def read(type : Int8?.class) : Int8?
    read(Int64?).try &.to_i8
  end

  def read(t : Int16.class) : Int16
    read(Int64).to_i16
  end

  def read(type : Int16?.class) : Int16?
    read(Int64?).try &.to_i16
  end

  def read(t : Int32.class) : Int32
    read(Int64).to_i32
  end

  def read(type : Int32?.class) : Int32?
    read(Int64?).try &.to_i32
  end

  def read(t : Float32.class) : Float32
    read(Float64).to_f32
  end

  def read(type : Float32?.class) : Float32?
    read(Float64?).try &.to_f32
  end

  def read(t : Time.class) : Time
    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

  def read(t : Time?.class) : Time?
    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

  def read(t : Bool.class) : Bool
    read(Int64) != 0
  end

  def read(t : Bool?.class) : Bool?
    read(Int64?).try &.!=(0)
  end

  def column_count : Int32
    LibSQLite3.column_count(self)
  end

  def column_name(index) : String
    String.new LibSQLite3.column_name(self, index)
  end

  def to_unsafe
    sqlite3_statement.to_unsafe
  end

  # :nodoc:
  private def step
    LibSQLite3::Code.new LibSQLite3.step(sqlite3_statement)
  end

  protected def sqlite3_statement
    @statement.as(Statement)
  end

  private def moving_column(&)
    res = yield @column_index
    @column_index += 1
    res
  end
end


================================================
FILE: src/sqlite3/statement.cr
================================================
class SQLite3::Statement < DB::Statement
  # Keep references to bound strings and bytes to prevent them from being
  # garbage collected before SQLite executes the statement. It is lazy
  # initialized to help with memory footprint.
  # See https://github.com/crystal-lang/crystal-sqlite3/pull/111 for more
  # context.
  @arg_refs : Array(Pointer(UInt8)) | Nil

  private def clear_arg_refs
    @arg_refs.try(&.clear)
  end

  private def track_arg_ref(ref)
    arg_refs = @arg_refs ||= [] of Pointer(UInt8)
    arg_refs.push(ref.to_unsafe)
  end

  def initialize(connection, command)
    super(connection, command)
    check LibSQLite3.prepare_v2(sqlite3_connection, command, command.bytesize + 1, out @stmt, nil)
  end

  protected def perform_query(args : Enumerable) : DB::ResultSet
    clear_arg_refs
    LibSQLite3.reset(self)
    args.each_with_index(1) do |arg, index|
      bind_arg(index, arg)
    end
    ResultSet.new(self)
  end

  protected def perform_exec(args : Enumerable) : DB::ExecResult
    clear_arg_refs
    LibSQLite3.reset(self.to_unsafe)
    args.each_with_index(1) do |arg, index|
      bind_arg(index, arg)
    end

    # exec
    step = uninitialized LibSQLite3::Code
    loop do
      step = LibSQLite3::Code.new LibSQLite3.step(self)
      break unless step == LibSQLite3::Code::ROW
    end
    raise Exception.new(sqlite3_connection) unless step == LibSQLite3::Code::DONE

    rows_affected = LibSQLite3.changes(sqlite3_connection).to_i64
    last_id = LibSQLite3.last_insert_rowid(sqlite3_connection)

    DB::ExecResult.new rows_affected, last_id
  end

  protected def do_close
    super
    clear_arg_refs
    check LibSQLite3.finalize(self)
  end

  private def bind_arg(index, value : Nil)
    check LibSQLite3.bind_null(self, index)
  end

  private def bind_arg(index, value : Bool)
    check LibSQLite3.bind_int(self, index, value ? 1 : 0)
  end

  private def bind_arg(index, value : UInt8)
    check LibSQLite3.bind_int(self, index, value.to_i)
  end

  private def bind_arg(index, value : UInt16)
    check LibSQLite3.bind_int(self, index, value.to_i)
  end

  private def bind_arg(index, value : UInt32)
    check LibSQLite3.bind_int64(self, index, value.to_i64)
  end

  private def bind_arg(index, value : Int8)
    check LibSQLite3.bind_int(self, index, value.to_i)
  end

  private def bind_arg(index, value : Int16)
    check LibSQLite3.bind_int(self, index, value.to_i)
  end

  private def bind_arg(index, value : Int32)
    check LibSQLite3.bind_int(self, index, value)
  end

  private def bind_arg(index, value : Int64)
    check LibSQLite3.bind_int64(self, index, value)
  end

  private def bind_arg(index, value : Float32)
    check LibSQLite3.bind_double(self, index, value.to_f64)
  end

  private def bind_arg(index, value : Float64)
    check LibSQLite3.bind_double(self, index, value)
  end

  private def bind_arg(index, value : String)
    track_arg_ref(value)
    check LibSQLite3.bind_text(self, index, value, value.bytesize, nil)
  end

  private def bind_arg(index, value : Bytes)
    track_arg_ref(value)
    check LibSQLite3.bind_blob(self, index, value, value.size, nil)
  end

  private def bind_arg(index, value : Time)
    bind_arg(index, value.in(SQLite3::TIME_ZONE).to_s(SQLite3::DATE_FORMAT_SUBSECOND))
  end

  private def bind_arg(index, value)
    raise "#{self.class} does not support #{value.class} params"
  end

  private def check(code)
    raise Exception.new(sqlite3_connection) unless code == 0
  end

  protected def sqlite3_connection
    @connection.as(Connection)
  end

  def to_unsafe
    @stmt
  end
end


================================================
FILE: src/sqlite3/type.cr
================================================
# Each of the possible types of an SQLite3 column.
enum SQLite3::Type
  INTEGER = 1
  FLOAT   = 2
  BLOB    = 4
  NULL    = 5
  TEXT    = 3
end


================================================
FILE: src/sqlite3/version.cr
================================================
module SQLite3
  VERSION = "0.23.0"
end


================================================
FILE: src/sqlite3.cr
================================================
require "db"
require "./sqlite3/**"

module SQLite3
  DATE_FORMAT_SUBSECOND = "%F %H:%M:%S.%L"
  DATE_FORMAT_SECOND    = "%F %H:%M:%S"

  alias Any = DB::Any | Int16 | Int8 | UInt32 | UInt16 | UInt8

  # :nodoc:
  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
Download .txt
gitextract_6uvl4mnq/

├── .circleci/
│   └── config.yml
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── compile_and_link_sqlite.md
├── samples/
│   └── memory.cr
├── shard.yml
├── spec/
│   ├── connection_spec.cr
│   ├── db_spec.cr
│   ├── driver_spec.cr
│   ├── pool_spec.cr
│   ├── result_set_spec.cr
│   └── spec_helper.cr
└── src/
    ├── sqlite3/
    │   ├── connection.cr
    │   ├── driver.cr
    │   ├── exception.cr
    │   ├── flags.cr
    │   ├── lib_sqlite3.cr
    │   ├── result_set.cr
    │   ├── statement.cr
    │   ├── type.cr
    │   └── version.cr
    └── sqlite3.cr
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (50K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 1119,
    "preview": "version: 2.1\n\norbs:\n  crystal: manastech/crystal@1.0\n\ncommands:\n  install-sqlite:\n    steps:\n      - run:\n          name"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 758,
    "preview": "name: CI\n\non:\n  push:\n  pull_request:\n    branches: [master]\n  schedule:\n    - cron: '0 6 * * 1'  # Every monday 6 AM\n\nj"
  },
  {
    "path": ".gitignore",
    "chars": 147,
    "preview": "/doc/\n/lib/\n/.crystal/\n/.shards/\n\n\n# Libraries don't need dependency lock\n# Dependencies will be locked in application t"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 4338,
    "preview": "## v0.23.0 (2026-04-12)\n\n* Use `URI#hostname` to avoid interpreting `:memory:` as `[:memory:]` ([#105](https://github.co"
  },
  {
    "path": "LICENSE",
    "chars": 1083,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Brian J. Cardiff\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README.md",
    "chars": 3155,
    "preview": "[![CI](https://github.com/crystal-lang/crystal-sqlite3/actions/workflows/ci.yml/badge.svg)](https://github.com/crystal-l"
  },
  {
    "path": "compile_and_link_sqlite.md",
    "chars": 2631,
    "preview": "# How to Compile And Link SQLite\n\nThere are two main reasons to compile SQLite from source and they are both about getti"
  },
  {
    "path": "samples/memory.cr",
    "chars": 689,
    "preview": "require \"db\"\nrequire \"../src/sqlite3\"\n\nDB.open \"sqlite3://%3Amemory%3A\" do |db|\n  db.exec \"create table contacts (name t"
  },
  {
    "path": "shard.yml",
    "chars": 252,
    "preview": "name: sqlite3\nversion: 0.23.0\n\ndependencies:\n  db:\n    github: crystal-lang/crystal-db\n    version: ~> 0.14.0\n\nauthors:\n"
  },
  {
    "path": "spec/connection_spec.cr",
    "chars": 3941,
    "preview": "require \"./spec_helper\"\n\nprivate def dump(source, target)\n  source.using_connection do |conn|\n    conn = conn.as(SQLite3"
  },
  {
    "path": "spec/db_spec.cr",
    "chars": 5698,
    "preview": "require \"./spec_helper\"\nrequire \"db/spec\"\n\nprivate class NotSupportedType\nend\n\nprivate def cast_if_blob(expr, sql_type)\n"
  },
  {
    "path": "spec/driver_spec.cr",
    "chars": 1167,
    "preview": "require \"./spec_helper\"\n\ndef assert_filename(uri, filename)\n  SQLite3::Connection.filename(URI.parse(uri)).should eq(fil"
  },
  {
    "path": "spec/pool_spec.cr",
    "chars": 903,
    "preview": "require \"./spec_helper\"\n\ndescribe DB::Pool do\n  it \"should write from multiple connections\" do\n    channel = Channel(Nil"
  },
  {
    "path": "spec/result_set_spec.cr",
    "chars": 1866,
    "preview": "require \"./spec_helper\"\n\ndescribe SQLite3::ResultSet do\n  it \"reads integer data types\" do\n    with_db do |db|\n      db."
  },
  {
    "path": "spec/spec_helper.cr",
    "chars": 733,
    "preview": "require \"spec\"\nrequire \"../src/sqlite3\"\n\ninclude SQLite3\n\nDB_FILENAME = \"./test.db\"\n\ndef with_db(&block : DB::Database -"
  },
  {
    "path": "src/sqlite3/connection.cr",
    "chars": 4061,
    "preview": "class SQLite3::Connection < DB::Connection\n  record Options,\n    filename : String = \":memory:\",\n    # pragmas\n    busy_"
  },
  {
    "path": "src/sqlite3/driver.cr",
    "chars": 574,
    "preview": "class SQLite3::Driver < DB::Driver\n  class ConnectionBuilder < ::DB::ConnectionBuilder\n    def initialize(@options : ::D"
  },
  {
    "path": "src/sqlite3/exception.cr",
    "chars": 266,
    "preview": "# Exception thrown on invalid SQLite3 operations.\nclass SQLite3::Exception < ::Exception\n  # The internal code associate"
  },
  {
    "path": "src/sqlite3/flags.cr",
    "chars": 1130,
    "preview": "@[Flags]\nenum SQLite3::Flag\n  READONLY       = 0x00000001 # Ok for sqlite3_open_v2()\n  READWRITE      = 0x00000002 # Ok "
  },
  {
    "path": "src/sqlite3/lib_sqlite3.cr",
    "chars": 4787,
    "preview": "require \"./type\"\n\n@[Link(\"sqlite3\")]\n{% if flag?(:msvc) %}\n  @[Link(dll: \"sqlite3.dll\")]\n{% end %}\nlib LibSQLite3\n  type"
  },
  {
    "path": "src/sqlite3/result_set.cr",
    "chars": 3467,
    "preview": "class SQLite3::ResultSet < DB::ResultSet\n  @column_index = 0\n\n  protected def do_close\n    LibSQLite3.reset(self)\n    su"
  },
  {
    "path": "src/sqlite3/statement.cr",
    "chars": 3602,
    "preview": "class SQLite3::Statement < DB::Statement\n  # Keep references to bound strings and bytes to prevent them from being\n  # g"
  },
  {
    "path": "src/sqlite3/type.cr",
    "chars": 144,
    "preview": "# Each of the possible types of an SQLite3 column.\nenum SQLite3::Type\n  INTEGER = 1\n  FLOAT   = 2\n  BLOB    = 4\n  NULL  "
  },
  {
    "path": "src/sqlite3/version.cr",
    "chars": 40,
    "preview": "module SQLite3\n  VERSION = \"0.23.0\"\nend\n"
  },
  {
    "path": "src/sqlite3.cr",
    "chars": 616,
    "preview": "require \"db\"\nrequire \"./sqlite3/**\"\n\nmodule SQLite3\n  DATE_FORMAT_SUBSECOND = \"%F %H:%M:%S.%L\"\n  DATE_FORMAT_SECOND    ="
  }
]

About this extraction

This page contains the full source code of the crystal-lang/crystal-sqlite3 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (46.1 KB), approximately 14.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!