[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\norbs:\n  crystal: manastech/crystal@1.0\n\ncommands:\n  install-sqlite:\n    steps:\n      - run:\n          name: Install `sqlite`\n          command: apt-get update && apt-get install -y libsqlite3-dev\n\njobs:\n  test:\n    parameters:\n      executor:\n        type: executor\n        default: crystal/default\n    executor: << parameters.executor >>\n    steps:\n      - install-sqlite\n      - crystal/version\n      - checkout\n      - crystal/with-shards-cache:\n          steps:\n            - crystal/shards-install\n      - crystal/spec\n      - crystal/format-check\n\nexecutors:\n  nightly:\n    docker:\n      - image: 'crystallang/crystal:nightly'\n    environment:\n      SHARDS_OPTS: --ignore-crystal-version\n\nworkflows:\n  version: 2\n\n  build:\n    jobs:\n      - test\n      - test:\n          name: test-on-nightly\n          executor:\n            name: nightly\n\n  nightly:\n    triggers:\n      - schedule:\n          cron: \"0 3 * * *\"\n          filters:\n            branches:\n              only:\n                - master\n    jobs:\n      - test:\n          name: test-on-nightly\n          executor:\n            name: nightly\n\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n    branches: [master]\n  schedule:\n    - cron: '0 6 * * 1'  # Every monday 6 AM\n\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest]\n        crystal: [1.0.0, latest, nightly]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Install Crystal\n        uses: crystal-lang/install-crystal@v1\n        with:\n          crystal: ${{ matrix.crystal }}\n\n      - name: Download source\n        uses: actions/checkout@v2\n\n      - name: Install shards\n        run: shards install\n\n      - name: Run specs\n        run: crystal spec\n\n      - name: Check formatting\n        run: crystal tool format; git diff --exit-code\n        if: matrix.crystal == 'latest' && matrix.os == 'ubuntu-latest'\n"
  },
  {
    "path": ".gitignore",
    "content": "/doc/\n/lib/\n/.crystal/\n/.shards/\n\n\n# Libraries don't need dependency lock\n# Dependencies will be locked in application that uses them\n/shard.lock\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## v0.23.0 (2026-04-12)\n\n* Use `URI#hostname` to avoid interpreting `:memory:` as `[:memory:]` ([#105](https://github.com/crystal-lang/crystal-sqlite3/pull/105), thanks @bcardiff)\n* Prevent arguments from being GC'd before statement is executed ([#111](https://github.com/crystal-lang/crystal-sqlite3/pull/111), thanks @lwakefield)\n* Add DLL name to `@[Link]` annotation ([#107](https://github.com/crystal-lang/crystal-sqlite3/pull/107), thanks @HertzDevil)\n* Replace Travis CI badge with GitHub Action ([#104](https://github.com/crystal-lang/crystal-sqlite3/pull/104), thanks @bcardiff)\n* Add another readme example ([#112](https://github.com/crystal-lang/crystal-sqlite3/pull/112), thanks @bcardiff)\n\n## v0.22.0 (2025-09-05)\n\n* 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)\n* Add docs on how to compile and link SQLite ([#74](https://github.com/crystal-lang/crystal-sqlite3/pull/74), thanks @chillfox)\n* Update to crystal-db ~> 0.14.0. ([#102](https://github.com/crystal-lang/crystal-sqlite3/pull/102), thanks @bcardiff)\n* Update formatting ([#100](https://github.com/crystal-lang/crystal-sqlite3/pull/100), thanks @straight-shoota)\n\n## v0.21.0 (2023-12-12)\n\n* Update to crystal-db ~> 0.13.0. ([#94](https://github.com/crystal-lang/crystal-sqlite3/pull/94))\n\n## v0.20.0 (2023-06-23)\n\n* Update to crystal-db ~> 0.12.0. ([#91](https://github.com/crystal-lang/crystal-sqlite3/pull/91))\n* Fix result set & connection release lifecycle. ([#90](https://github.com/crystal-lang/crystal-sqlite3/pull/90))\n* Automatically set PRAGMAs using connection query params. ([#85](https://github.com/crystal-lang/crystal-sqlite3/pull/85), thanks @luislavena)\n\n## v0.19.0 (2022-01-28)\n\n* Update to crystal-db ~> 0.11.0. ([#77](https://github.com/crystal-lang/crystal-sqlite3/pull/77))\n* Fix timestamps support to allow dealing with exact seconds values ([#68](https://github.com/crystal-lang/crystal-sqlite3/pull/68), thanks @yujiri8, @tenebrousedge)\n* Migrate CI to GitHub Actions. ([#78](https://github.com/crystal-lang/crystal-sqlite3/pull/78))\n\nThis release requires Crystal 1.0.0 or later.\n\n## v0.18.0 (2021-01-26)\n\n* Add `REGEXP` support powered by Crystal's std-lib Regex. ([#62](https://github.com/crystal-lang/crystal-sqlite3/pull/62), thanks @yujiri8)\n\n## v0.17.0 (2020-09-30)\n\n* Update to crystal-db ~> 0.10.0. ([#58](https://github.com/crystal-lang/crystal-sqlite3/pull/58))\n\nThis release requires Crystal 0.35.0 or later.\n\n## v0.16.0 (2020-04-06)\n\n* Update to crystal-db ~> 0.9.0. ([#55](https://github.com/crystal-lang/crystal-sqlite3/pull/55))\n\n## v0.15.0 (2019-12-11)\n\n* Update to crystal-db ~> 0.8.0. ([#50](https://github.com/crystal-lang/crystal-sqlite3/pull/50))\n\n## v0.14.0 (2019-09-23)\n\n* Update to crystal-db ~> 0.7.0. ([#44](https://github.com/crystal-lang/crystal-sqlite3/pull/44))\n\n## v0.13.0 (2019-08-02)\n\n* Fix compatibility issues for Crystal 0.30.0. ([#43](https://github.com/crystal-lang/crystal-sqlite3/pull/43))\n\n## v0.12.0 (2019-06-07)\n\nThis release requires crystal >= 0.28.0\n\n* Fix compatibility issues for crystal 0.29.0 ([#40](https://github.com/crystal-lang/crystal-sqlite3/pull/40))\n\n## v0.11.0 (2019-04-18)\n\n* Fix compatibility issues for crystal 0.28.0 ([#38](https://github.com/crystal-lang/crystal-sqlite3/pull/38))\n* Add complete list of `LibSQLite3::Code` values. ([#36](https://github.com/crystal-lang/crystal-sqlite3/pull/36), thanks @t-richards)\n\n## v0.10.0 (2018-06-18)\n\n* Fix compatibility issues for crystal 0.25.0 ([#34](https://github.com/crystal-lang/crystal-sqlite3/pull/34))\n  * All the time instances are translated to UTC before saving them in the db\n\n## v0.9.0 (2017-12-31)\n\n* Update to crystal-db ~> 0.5.0\n\n## v0.8.3 (2017-11-07)\n\n* Update to crystal-db ~> 0.4.1\n* Add `SQLite3::VERSION` constant with shard version.\n* Add support for multi-steps statements execution. (see [#27](https://github.com/crystal-lang/crystal-sqlite3/pull/27), thanks @t-richards)\n* Fix how resources are released. (see [#23](https://github.com/crystal-lang/crystal-sqlite3/pull/23), thanks @benoist)\n* Fix blob c bindings. (see [#28](https://github.com/crystal-lang/crystal-sqlite3/pull/28), thanks @rufusroflpunch)\n\n## v0.8.2 (2017-03-21)\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Brian J. Cardiff\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![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)\n\n# crystal-sqlite3\n\nSQLite3 bindings for [Crystal](http://crystal-lang.org/).\n\nCheck [crystal-db](https://github.com/crystal-lang/crystal-db) for general db driver documentation. crystal-sqlite3 driver is registered under `sqlite3://` uri.\n\n## Installation\n\nAdd this to your application's `shard.yml`:\n\n```yml\ndependencies:\n  sqlite3:\n    github: crystal-lang/crystal-sqlite3\n```\n\n### Usage\n\n```crystal\nrequire \"sqlite3\"\n\nDB.open \"sqlite3://./data.db\" do |db|\n  db.exec \"create table contacts (name text, age integer)\"\n  db.exec \"insert into contacts values (?, ?)\", \"John Doe\", 30\n\n  args = [] of DB::Any\n  args << \"Sarah\"\n  args << 33\n  db.exec \"insert into contacts values (?, ?)\", args: args\n\n  puts \"max age:\"\n  puts db.scalar \"select max(age) from contacts\" # => 33\n\n  puts \"contacts:\"\n  db.query \"select name, age from contacts order by age desc\" do |rs|\n    puts \"#{rs.column_name(0)} (#{rs.column_name(1)})\"\n    # => name (age)\n    rs.each do\n      puts \"#{rs.read(String)} (#{rs.read(Int32)})\"\n      # => Sarah (33)\n      # => John Doe (30)\n    end\n  end\n\n  contacts = db.query_all \"SELECT name, age FROM contacts\", as: {name: String, age: Int64}\n  puts contacts # => [{name: \"John Doe\", age: 30}, {name: \"Sarah\", age: 33}]\nend\n```\n\n### DB::Any\n\n* `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).\n* `Bool` is implemented as `INT` column mapping `0`/`1` values.\n\n### Setting PRAGMAs\n\nYou can adjust certain [SQLite3 PRAGMAs](https://www.sqlite.org/pragma.html)\nautomatically when the connection is created by using the query parameters:\n\n```crystal\nrequire \"sqlite3\"\n\nDB.open \"sqlite3://./data.db?journal_mode=wal&synchronous=normal\" do |db|\n  # this database now uses WAL journal and normal synchronous mode\n  # (defaults were `delete` and `full`, respectively)\nend\n```\n\nThe following is the list of supported options:\n\n| Name                      | Connection key  |\n|---------------------------|-----------------|\n| [Busy Timeout][pragma-to] | `busy_timeout` |\n| [Cache Size][pragma-cs] | `cache_size` |\n| [Foreign Keys][pragma-fk] | `foreign_keys` |\n| [Journal Mode][pragma-jm] | `journal_mode` |\n| [Synchronous][pragma-sync] | `synchronous` |\n| [WAL autocheckoint][pragma-walck] | `wal_autocheckpoint` |\n\nPlease note there values passed using these connection keys are passed\ndirectly to SQLite3 without check or evaluation. Using incorrect values result\nin no error by the library.\n\n[pragma-to]: https://www.sqlite.org/pragma.html#pragma_busy_timeout\n[pragma-cs]: https://www.sqlite.org/pragma.html#pragma_cache_size\n[pragma-fk]: https://www.sqlite.org/pragma.html#pragma_foreign_keys\n[pragma-jm]: https://www.sqlite.org/pragma.html#pragma_journal_mode\n[pragma-sync]: https://www.sqlite.org/pragma.html#pragma_synchronous\n[pragma-walck]: https://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint\n\n## Guides\n\n- [Compile and link SQLite](compile_and_link_sqlite.md)\n"
  },
  {
    "path": "compile_and_link_sqlite.md",
    "content": "# How to Compile And Link SQLite\n\nThere are two main reasons to compile SQLite from source and they are both about getting features that are otherwise unavailable.\n\n- 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.\n- Perhaps you want some compile time features enabled that are not commonly enabled by default.\n\nThis guide assumes the first reason and goes through how to compile the latest release.\n\n\n## Install Prerequisites (Ubuntu)\n\nOn Ubuntu you will need build-essential installed at a minimum.\n\n```sh\nsudo apt update\nsudo apt install build-essential\n```\n\n\n## Download And Extract The Source Code\n\nSource code for the latest release can be downloaded from the [SQLite Download Page](https://sqlite.org/download.html).\nLook for \"C source code as an amalgamation\", It should be the first one on the page.\n\n```sh\nwget https://sqlite.org/2021/sqlite-amalgamation-3370000.zip\nunzip sqlite-amalgamation-3370000.zip\ncd sqlite-amalgamation-3370000\n```\n\n\n## Compile SQLite\n\nCompile the sqlite command.\n\n```sh\ngcc shell.c sqlite3.c -lpthread -ldl -o sqlite3\n./sqlite3 --version\n```\n\nCompile libsqlite.\n\n```sh\ngcc -lpthread -ldl -shared -o libsqlite3.so.0 -fPIC sqlite3.c\n```\n\n## Using The New Version of SQLite\n\nThe path to libsqlite can be specified at runtime with \"LD_LIBRARY_PATH\".\n\n```sh\n# directory of your crystal app\ncd ../app\n\n# Crystal run\nLD_LIBRARY_PATH=../sqlite-amalgamation-3370000 crystal run src/app.cr\n\n# This way will allow specifying the library location at runtime if it is different from the system default.\ncrystal build --release --link-flags -L\"$(realpath ../sqlite-amalgamation-3370000/libsqlite3.so.0)\" src/app.cr\nLD_LIBRARY_PATH=../sqlite-amalgamation-3370000 ./app\n\n# ldd can be used to see which libsqlite is being linked\nLD_LIBRARY_PATH=../sqlite-amalgamation-3370000 ldd ./app\n```\n\nOr the absolute path to libsqlite can be specified at compile time.\n\n```sh\ncrystal run --link-flags \"$(realpath ../sqlite-amalgamation-3370000/libsqlite3.so.0)\" src/app.cr\n\n# This will create a version that only works if libsqlite in the excact same location as when it was compiled.\ncrystal build --release --link-flags \"$(realpath ../sqlite-amalgamation-3370000/libsqlite3.so.0)\" src/app.cr\n./app\n\n# Use ldd to see which libsqlite is being linked\nldd ./app\n```\n\n\n## Check SQLite Version From Crystal\n\nTo check which version of SQLite is being used from Crystal.\n\n```crystal\n# src/app.cr\n\nDB_URI = \"sqlite3://:memory:\"\n\nDB.open DB_URI do |db|\n    db_version = db.scalar \"select sqlite_version();\"\n    puts \"SQLite #{db_version}\"\nend\n```\n"
  },
  {
    "path": "samples/memory.cr",
    "content": "require \"db\"\nrequire \"../src/sqlite3\"\n\nDB.open \"sqlite3://%3Amemory%3A\" do |db|\n  db.exec \"create table contacts (name text, age integer)\"\n  db.exec \"insert into contacts values (?, ?)\", \"John Doe\", 30\n\n  args = [] of DB::Any\n  args << \"Sarah\"\n  args << 33\n  db.exec \"insert into contacts values (?, ?)\", args: args\n\n  puts \"max age:\"\n  puts db.scalar \"select max(age) from contacts\" # => 33\n\n  puts \"contacts:\"\n  db.query \"select name, age from contacts order by age desc\" do |rs|\n    puts \"#{rs.column_name(0)} (#{rs.column_name(1)})\"\n    # => name (age)\n    rs.each do\n      puts \"#{rs.read(String)} (#{rs.read(Int32)})\"\n      # => Sarah (33)\n      # => John Doe (30)\n    end\n  end\nend\n"
  },
  {
    "path": "shard.yml",
    "content": "name: sqlite3\nversion: 0.23.0\n\ndependencies:\n  db:\n    github: crystal-lang/crystal-db\n    version: ~> 0.14.0\n\nauthors:\n  - Ary Borenszweig <aborenszweig@manas.tech>\n  - Brian J. Cardiff <bcardiff@gmail.com>\n\ncrystal: \">= 1.0.0, < 2.0.0\"\n\nlicense: MIT\n"
  },
  {
    "path": "spec/connection_spec.cr",
    "content": "require \"./spec_helper\"\n\nprivate def dump(source, target)\n  source.using_connection do |conn|\n    conn = conn.as(SQLite3::Connection)\n    target.using_connection do |backup_conn|\n      backup_conn = backup_conn.as(SQLite3::Connection)\n      conn.dump(backup_conn)\n    end\n  end\nend\n\nprivate def it_sets_pragma_on_connection(pragma : String, value : String, expected, file = __FILE__, line = __LINE__)\n  it \"sets pragma '#{pragma}' to #{expected}\", file, line do\n    with_db(\"#{DB_FILENAME}?#{pragma}=#{value}\") do |db|\n      db.scalar(\"PRAGMA #{pragma}\").should eq(expected)\n    end\n  end\nend\n\ndescribe Connection do\n  it \"opens a database and then backs it up to another db\" do\n    with_db do |db|\n      with_db(\"./test2.db\") do |backup_db|\n        db.exec \"create table person (name text, age integer)\"\n        db.exec \"insert into person values (\\\"foo\\\", 10)\"\n\n        dump db, backup_db\n\n        backup_name = backup_db.scalar \"select name from person\"\n        backup_age = backup_db.scalar \"select age from person\"\n        source_name = db.scalar \"select name from person\"\n        source_age = db.scalar \"select age from person\"\n\n        {backup_name, backup_age}.should eq({source_name, source_age})\n      end\n    end\n  end\n\n  it \"opens a database, inserts records, dumps to an in-memory db, insers some more, then dumps to the source\" do\n    with_db do |db|\n      with_mem_db do |in_memory_db|\n        db.exec \"create table person (name text, age integer)\"\n        db.exec \"insert into person values (\\\"foo\\\", 10)\"\n        dump db, in_memory_db\n\n        in_memory_db.scalar(\"select count(*) from person\").should eq(1)\n        in_memory_db.exec \"insert into person values (\\\"bar\\\", 22)\"\n        dump in_memory_db, db\n\n        db.scalar(\"select count(*) from person\").should eq(2)\n      end\n    end\n  end\n\n  it \"opens a database, inserts records (>1024K), and dumps to an in-memory db\" do\n    with_db do |db|\n      with_mem_db do |in_memory_db|\n        db.exec \"create table person (name text, age integer)\"\n        db.transaction do |tx|\n          100_000.times { tx.connection.exec \"insert into person values (\\\"foo\\\", 10)\" }\n        end\n        dump db, in_memory_db\n        in_memory_db.scalar(\"select count(*) from person\").should eq(100_000)\n      end\n    end\n  end\n\n  it \"opens a connection without the pool\" do\n    with_cnn do |cnn|\n      cnn.should be_a(SQLite3::Connection)\n\n      cnn.exec \"create table person (name text, age integer)\"\n      cnn.exec \"insert into person values (\\\"foo\\\", 10)\"\n\n      cnn.scalar(\"select count(*) from person\").should eq(1)\n    end\n  end\n\n  # adjust busy_timeout pragma (default is 0)\n  it_sets_pragma_on_connection \"busy_timeout\", \"1000\", 1000\n\n  # adjust cache_size pragma (default is -2000, 2MB)\n  it_sets_pragma_on_connection \"cache_size\", \"-4000\", -4000\n\n  # enable foreign_keys, no need to test off (is the default)\n  it_sets_pragma_on_connection \"foreign_keys\", \"1\", 1\n  it_sets_pragma_on_connection \"foreign_keys\", \"yes\", 1\n  it_sets_pragma_on_connection \"foreign_keys\", \"true\", 1\n  it_sets_pragma_on_connection \"foreign_keys\", \"on\", 1\n\n  # change journal_mode (default is delete)\n  it_sets_pragma_on_connection \"journal_mode\", \"delete\", \"delete\"\n  it_sets_pragma_on_connection \"journal_mode\", \"truncate\", \"truncate\"\n  it_sets_pragma_on_connection \"journal_mode\", \"persist\", \"persist\"\n\n  # change synchronous mode (default is 2, FULL)\n  it_sets_pragma_on_connection \"synchronous\", \"0\", 0\n  it_sets_pragma_on_connection \"synchronous\", \"off\", 0\n  it_sets_pragma_on_connection \"synchronous\", \"1\", 1\n  it_sets_pragma_on_connection \"synchronous\", \"normal\", 1\n  it_sets_pragma_on_connection \"synchronous\", \"2\", 2\n  it_sets_pragma_on_connection \"synchronous\", \"full\", 2\n  it_sets_pragma_on_connection \"synchronous\", \"3\", 3\n  it_sets_pragma_on_connection \"synchronous\", \"extra\", 3\n\n  # change wal_autocheckpoint (default is 1000)\n  it_sets_pragma_on_connection \"wal_autocheckpoint\", \"0\", 0\nend\n"
  },
  {
    "path": "spec/db_spec.cr",
    "content": "require \"./spec_helper\"\nrequire \"db/spec\"\n\nprivate class NotSupportedType\nend\n\nprivate def cast_if_blob(expr, sql_type)\n  case sql_type\n  when \"blob\"\n    \"cast(#{expr} as blob)\"\n  else\n    expr\n  end\nend\n\nDB::DriverSpecs(SQLite3::Any).run do |ctx|\n  support_unprepared false\n\n  before do\n    File.delete(DB_FILENAME) if File.exists?(DB_FILENAME)\n  end\n  after do\n    File.delete(DB_FILENAME) if File.exists?(DB_FILENAME)\n  end\n\n  connection_string \"sqlite3:#{DB_FILENAME}\"\n  # ? can use many ... (:memory:)\n\n  sample_value true, \"int\", \"1\", type_safe_value: false\n  sample_value false, \"int\", \"0\", type_safe_value: false\n  sample_value 2, \"int\", \"2\", type_safe_value: false\n  sample_value 1_i64, \"int\", \"1\"\n  sample_value \"hello\", \"text\", \"'hello'\"\n  sample_value 1.5_f32, \"float\", \"1.5\", type_safe_value: false\n  sample_value 1.5, \"float\", \"1.5\"\n  sample_value Time.utc(2016, 2, 15), \"text\", \"'2016-02-15 00:00:00.000'\", type_safe_value: false\n  sample_value Time.utc(2016, 2, 15, 10, 15, 30), \"text\", \"'2016-02-15 10:15:30'\", type_safe_value: false\n  sample_value Time.utc(2016, 2, 15, 10, 15, 30), \"text\", \"'2016-02-15 10:15:30.000'\", type_safe_value: false\n  sample_value Time.utc(2016, 2, 15, 10, 15, 30, nanosecond: 123000000), \"text\", \"'2016-02-15 10:15:30.123'\", type_safe_value: false\n  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\n  sample_value Int8::MIN, \"int\", Int8::MIN.to_s, type_safe_value: false\n  sample_value Int8::MAX, \"int\", Int8::MAX.to_s, type_safe_value: false\n  sample_value UInt8::MIN, \"int\", UInt8::MIN.to_s, type_safe_value: false\n  sample_value UInt8::MAX, \"int\", UInt8::MAX.to_s, type_safe_value: false\n  sample_value Int16::MIN, \"int\", Int16::MIN.to_s, type_safe_value: false\n  sample_value Int16::MAX, \"int\", Int16::MAX.to_s, type_safe_value: false\n  sample_value UInt16::MIN, \"int\", UInt16::MIN.to_s, type_safe_value: false\n  sample_value UInt16::MAX, \"int\", UInt16::MAX.to_s, type_safe_value: false\n  sample_value Int32::MIN, \"int\", Int32::MIN.to_s, type_safe_value: false\n  sample_value Int32::MAX, \"int\", Int32::MAX.to_s, type_safe_value: false\n  sample_value UInt32::MIN, \"int\", UInt32::MIN.to_s, type_safe_value: false\n  sample_value UInt32::MAX, \"int\", UInt32::MAX.to_s, type_safe_value: false\n  sample_value Int64::MIN, \"int\", Int64::MIN.to_s, type_safe_value: false\n  sample_value Int64::MAX, \"int\", Int64::MAX.to_s, type_safe_value: false\n\n  ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65]\n  sample_value Bytes.new(ary.to_unsafe, ary.size), \"blob\", \"X'53514C697465'\" # , type_safe_value: false\n\n  binding_syntax do |index|\n    \"?\"\n  end\n\n  create_table_1column_syntax do |table_name, col1|\n    \"create table #{table_name} (#{col1.name} #{col1.sql_type} #{col1.null ? \"NULL\" : \"NOT NULL\"})\"\n  end\n\n  create_table_2columns_syntax do |table_name, col1, col2|\n    \"create table #{table_name} (#{col1.name} #{col1.sql_type} #{col1.null ? \"NULL\" : \"NOT NULL\"}, #{col2.name} #{col2.sql_type} #{col2.null ? \"NULL\" : \"NOT NULL\"})\"\n  end\n\n  select_1column_syntax do |table_name, col1|\n    \"select #{cast_if_blob(col1.name, col1.sql_type)} from #{table_name}\"\n  end\n\n  select_2columns_syntax do |table_name, col1, col2|\n    \"select #{cast_if_blob(col1.name, col1.sql_type)}, #{cast_if_blob(col2.name, col2.sql_type)} from #{table_name}\"\n  end\n\n  select_count_syntax do |table_name|\n    \"select count(*) from #{table_name}\"\n  end\n\n  select_scalar_syntax do |expression, sql_type|\n    \"select #{cast_if_blob(expression, sql_type)}\"\n  end\n\n  insert_1column_syntax do |table_name, col, expression|\n    \"insert into #{table_name} (#{col.name}) values (#{expression})\"\n  end\n\n  insert_2columns_syntax do |table_name, col1, expr1, col2, expr2|\n    \"insert into #{table_name} (#{col1.name}, #{col2.name}) values (#{expr1}, #{expr2})\"\n  end\n\n  drop_table_if_exists_syntax do |table_name|\n    \"drop table if exists #{table_name}\"\n  end\n\n  it \"gets last insert row id\", prepared: :both do |db|\n    db.exec \"create table person (name text, age integer)\"\n    db.exec %(insert into person values (\"foo\", 10))\n    res = db.exec %(insert into person values (\"foo\", 10))\n    res.last_insert_id.should eq(2)\n    res.rows_affected.should eq(1)\n  end\n\n  # TODO timestamp support\n\n  it \"raises on unsupported param types\" do |db|\n    expect_raises Exception, \"SQLite3::Statement does not support NotSupportedType params\" do\n      db.query \"select ?\", NotSupportedType.new\n    end\n    # TODO raising exception does not close the connection and pool is exhausted\n  end\n\n  it \"ensures statements are closed\" do |db|\n    db.exec %(create table if not exists a (i int not null, str text not null);)\n    db.exec %(insert into a (i, str) values (23, \"bai bai\");)\n\n    2.times do |i|\n      DB.open ctx.connection_string do |db|\n        begin\n          db.query(\"SELECT i, str FROM a WHERE i = ?\", 23) do |rs|\n            rs.move_next\n            break\n          end\n        rescue e : SQLite3::Exception\n          fail(\"Expected no exception, but got \\\"#{e.message}\\\"\")\n        end\n\n        begin\n          db.exec(\"UPDATE a SET i = ? WHERE i = ?\", 23, 23)\n        rescue e : SQLite3::Exception\n          fail(\"Expected no exception, but got \\\"#{e.message}\\\"\")\n        end\n      end\n    end\n  end\n\n  it \"handles single-step pragma statements\" do |db|\n    db.exec %(PRAGMA synchronous = OFF)\n  end\n\n  it \"handles multi-step pragma statements\" do |db|\n    db.exec %(PRAGMA journal_mode = memory)\n  end\n\n  it \"handles REGEXP operator\" do |db|\n    (db.scalar \"select 'unmatching text' REGEXP '^m'\").should eq 0\n    (db.scalar \"select 'matching text' REGEXP '^m'\").should eq 1\n  end\nend\n"
  },
  {
    "path": "spec/driver_spec.cr",
    "content": "require \"./spec_helper\"\n\ndef assert_filename(uri, filename)\n  SQLite3::Connection.filename(URI.parse(uri)).should eq(filename)\nend\n\ndescribe Driver do\n  it \"should register sqlite3 name\" do\n    DB.driver_class(\"sqlite3\").should eq(SQLite3::Driver)\n  end\n\n  it \"should get filename from uri\" do\n    assert_filename(\"sqlite3:%3Amemory%3A\", \":memory:\")\n    assert_filename(\"sqlite3://%3Amemory%3A\", \":memory:\")\n\n    assert_filename(\"sqlite3:./file.db\", \"./file.db\")\n    assert_filename(\"sqlite3://./file.db\", \"./file.db\")\n\n    assert_filename(\"sqlite3:/path/to/file.db\", \"/path/to/file.db\")\n    assert_filename(\"sqlite3:///path/to/file.db\", \"/path/to/file.db\")\n\n    assert_filename(\"sqlite3:./file.db?max_pool_size=5\", \"./file.db\")\n    assert_filename(\"sqlite3:/path/to/file.db?max_pool_size=5\", \"/path/to/file.db\")\n    assert_filename(\"sqlite3://./file.db?max_pool_size=5\", \"./file.db\")\n    assert_filename(\"sqlite3:///path/to/file.db?max_pool_size=5\", \"/path/to/file.db\")\n  end\n\n  it \"should use database option as file to open\" do\n    with_db do |db|\n      db.checkout.should be_a(SQLite3::Connection)\n      File.exists?(DB_FILENAME).should be_true\n    end\n  end\nend\n"
  },
  {
    "path": "spec/pool_spec.cr",
    "content": "require \"./spec_helper\"\n\ndescribe DB::Pool do\n  it \"should write from multiple connections\" do\n    channel = Channel(Nil).new\n    fibers = 5\n    max_n = 50\n    with_db \"#{DB_FILENAME}?max_pool_size=#{fibers}\" do |db|\n      db.exec \"create table numbers (n integer, fiber integer)\"\n\n      fibers.times do |f|\n        spawn do\n          (1..max_n).each do |n|\n            db.exec \"insert into numbers (n, fiber) values (?, ?)\", n, f\n            sleep 0.01\n          end\n          channel.send nil\n        end\n      end\n\n      fibers.times { channel.receive }\n\n      # all numbers were inserted\n      s = fibers * max_n * (max_n + 1) // 2\n      db.scalar(\"select sum(n) from numbers\").should eq(s)\n\n      # numbers were not inserted one fiber at a time\n      rows = db.query_all \"select n, fiber from numbers\", as: {Int32, Int32}\n      rows.map(&.[1]).should_not eq(rows.map(&.[1]).sort)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/result_set_spec.cr",
    "content": "require \"./spec_helper\"\n\ndescribe SQLite3::ResultSet do\n  it \"reads integer data types\" do\n    with_db do |db|\n      db.exec \"CREATE TABLE test_table (test_int integer)\"\n      db.exec \"INSERT INTO test_table (test_int) values (?)\", 42\n      db.query(\"SELECT test_int FROM test_table\") do |rs|\n        rs.each do\n          rs.read.should eq(42)\n        end\n      end\n    end\n  end\n\n  it \"reads string data types\" do\n    with_db do |db|\n      db.exec \"CREATE TABLE test_table (test_text text)\"\n      db.exec \"INSERT INTO test_table (test_text) values (?), (?)\", \"abc\", \"123\"\n      db.query(\"SELECT test_text FROM test_table\") do |rs|\n        rs.each do\n          rs.read.should match(/abc|123/)\n        end\n      end\n    end\n  end\n\n  it \"reads time data types\" do\n    with_db do |db|\n      db.exec \"CREATE TABLE test_table (test_date datetime)\"\n      timestamp = Time.utc\n      db.exec \"INSERT INTO test_table (test_date) values (current_timestamp)\"\n      db.query(\"SELECT test_date FROM test_table\") do |rs|\n        rs.each do\n          rs.read(Time).should be_close(timestamp, 1.second)\n        end\n      end\n    end\n  end\n\n  it \"reads time stored in text fields, too\" do\n    with_db do |db|\n      db.exec \"CREATE TABLE test_table (test_date text)\"\n      timestamp = Time.utc\n      # Try 3 different ways: our own two formats and using SQLite's current_timestamp.\n      # They should all work.\n      db.exec \"INSERT INTO test_table (test_date) values (?)\", timestamp.to_s SQLite3::DATE_FORMAT_SUBSECOND\n      db.exec \"INSERT INTO test_table (test_date) values (?)\", timestamp.to_s SQLite3::DATE_FORMAT_SECOND\n      db.exec \"INSERT INTO test_table (test_date) values (current_timestamp)\"\n      db.query(\"SELECT test_date FROM test_table\") do |rs|\n        rs.each do\n          rs.read(Time).should be_close(timestamp, 1.second)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.cr",
    "content": "require \"spec\"\nrequire \"../src/sqlite3\"\n\ninclude SQLite3\n\nDB_FILENAME = \"./test.db\"\n\ndef with_db(&block : DB::Database ->)\n  File.delete(DB_FILENAME) rescue nil\n  DB.open \"sqlite3:#{DB_FILENAME}\", &block\nensure\n  File.delete(DB_FILENAME)\nend\n\ndef with_cnn(&block : DB::Connection ->)\n  File.delete(DB_FILENAME) rescue nil\n  DB.connect \"sqlite3:#{DB_FILENAME}\", &block\nensure\n  File.delete(DB_FILENAME)\nend\n\ndef with_db(config, &block : DB::Database ->)\n  uri = \"sqlite3:#{config}\"\n  filename = SQLite3::Connection.filename(URI.parse(uri))\n  File.delete(filename) rescue nil\n  DB.open uri, &block\nensure\n  File.delete(filename) if filename\nend\n\ndef with_mem_db(&block : DB::Database ->)\n  DB.open \"sqlite3://%3Amemory%3A\", &block\nend\n"
  },
  {
    "path": "src/sqlite3/connection.cr",
    "content": "class SQLite3::Connection < DB::Connection\n  record Options,\n    filename : String = \":memory:\",\n    # pragmas\n    busy_timeout : String? = nil,\n    cache_size : String? = nil,\n    foreign_keys : String? = nil,\n    journal_mode : String? = nil,\n    synchronous : String? = nil,\n    wal_autocheckpoint : String? = nil do\n    def self.from_uri(uri : URI, default = Options.new)\n      params = HTTP::Params.parse(uri.query || \"\")\n\n      Options.new(\n        filename: URI.decode_www_form((uri.hostname || \"\") + uri.path),\n        # pragmas\n        busy_timeout: params.fetch(\"busy_timeout\", default.busy_timeout),\n        cache_size: params.fetch(\"cache_size\", default.cache_size),\n        foreign_keys: params.fetch(\"foreign_keys\", default.foreign_keys),\n        journal_mode: params.fetch(\"journal_mode\", default.journal_mode),\n        synchronous: params.fetch(\"synchronous\", default.synchronous),\n        wal_autocheckpoint: params.fetch(\"wal_autocheckpoint\", default.wal_autocheckpoint),\n      )\n    end\n\n    def pragma_statement\n      res = String.build do |str|\n        pragma_append(str, \"busy_timeout\", busy_timeout)\n        pragma_append(str, \"cache_size\", cache_size)\n        pragma_append(str, \"foreign_keys\", foreign_keys)\n        pragma_append(str, \"journal_mode\", journal_mode)\n        pragma_append(str, \"synchronous\", synchronous)\n        pragma_append(str, \"wal_autocheckpoint\", wal_autocheckpoint)\n      end\n\n      res.empty? ? nil : res\n    end\n\n    private def pragma_append(io, key, value)\n      return unless value\n      io << \"PRAGMA #{key}=#{value};\"\n    end\n  end\n\n  def initialize(options : ::DB::Connection::Options, sqlite3_options : Options)\n    super(options)\n    check LibSQLite3.open_v2(sqlite3_options.filename, out @db, (Flag::READWRITE | Flag::CREATE), nil)\n    # 2 means 2 arguments; 1 is the code for UTF-8\n    check LibSQLite3.create_function(@db, \"regexp\", 2, 1, nil, SQLite3::REGEXP_FN, nil, nil)\n\n    if pragma_statement = sqlite3_options.pragma_statement\n      check LibSQLite3.exec(@db, pragma_statement, nil, nil, nil)\n    end\n  rescue\n    raise DB::ConnectionRefused.new\n  end\n\n  def self.filename(uri : URI)\n    URI.decode_www_form((uri.hostname || \"\") + uri.path)\n  end\n\n  def build_prepared_statement(query) : Statement\n    Statement.new(self, query)\n  end\n\n  def build_unprepared_statement(query) : Statement\n    # sqlite3 does not support unprepared statement.\n    # All statements once prepared should be released\n    # when unneeded. Unprepared statement are not aim\n    # to leave state in the connection. Mimicking them\n    # with prepared statement would be wrong with\n    # respect connection resources.\n    raise DB::Error.new(\"SQLite3 driver does not support unprepared statements\")\n  end\n\n  def do_close\n    super\n    check LibSQLite3.close(self)\n  end\n\n  # :nodoc:\n  def perform_begin_transaction\n    self.prepared.exec \"BEGIN\"\n  end\n\n  # :nodoc:\n  def perform_commit_transaction\n    self.prepared.exec \"COMMIT\"\n  end\n\n  # :nodoc:\n  def perform_rollback_transaction\n    self.prepared.exec \"ROLLBACK\"\n  end\n\n  # :nodoc:\n  def perform_create_savepoint(name)\n    self.prepared.exec \"SAVEPOINT #{name}\"\n  end\n\n  # :nodoc:\n  def perform_release_savepoint(name)\n    self.prepared.exec \"RELEASE SAVEPOINT #{name}\"\n  end\n\n  # :nodoc:\n  def perform_rollback_savepoint(name)\n    self.prepared.exec \"ROLLBACK TO #{name}\"\n  end\n\n  # Dump the database to another SQLite3 database. This can be used for backing up a SQLite3 Database\n  # to disk or the opposite\n  def dump(to : SQLite3::Connection)\n    backup_item = LibSQLite3.backup_init(to.@db, \"main\", @db, \"main\")\n    if backup_item.null?\n      raise Exception.new(to.@db)\n    end\n    code = LibSQLite3.backup_step(backup_item, -1)\n\n    if code != LibSQLite3::Code::DONE\n      raise Exception.new(to.@db)\n    end\n    code = LibSQLite3.backup_finish(backup_item)\n    if code != LibSQLite3::Code::OKAY\n      raise Exception.new(to.@db)\n    end\n  end\n\n  def to_unsafe\n    @db\n  end\n\n  private def check(code)\n    raise Exception.new(self) unless code == 0\n  end\nend\n"
  },
  {
    "path": "src/sqlite3/driver.cr",
    "content": "class SQLite3::Driver < DB::Driver\n  class ConnectionBuilder < ::DB::ConnectionBuilder\n    def initialize(@options : ::DB::Connection::Options, @sqlite3_options : SQLite3::Connection::Options)\n    end\n\n    def build : ::DB::Connection\n      SQLite3::Connection.new(@options, @sqlite3_options)\n    end\n  end\n\n  def connection_builder(uri : URI) : ::DB::ConnectionBuilder\n    params = HTTP::Params.parse(uri.query || \"\")\n    ConnectionBuilder.new(connection_options(params), SQLite3::Connection::Options.from_uri(uri))\n  end\nend\n\nDB.register_driver \"sqlite3\", SQLite3::Driver\n"
  },
  {
    "path": "src/sqlite3/exception.cr",
    "content": "# Exception thrown on invalid SQLite3 operations.\nclass SQLite3::Exception < ::Exception\n  # The internal code associated with the failure.\n  getter code\n\n  def initialize(db)\n    super(String.new(LibSQLite3.errmsg(db)))\n    @code = LibSQLite3.errcode(db)\n  end\nend\n"
  },
  {
    "path": "src/sqlite3/flags.cr",
    "content": "@[Flags]\nenum SQLite3::Flag\n  READONLY       = 0x00000001 # Ok for sqlite3_open_v2()\n  READWRITE      = 0x00000002 # Ok for sqlite3_open_v2()\n  CREATE         = 0x00000004 # Ok for sqlite3_open_v2()\n  DELETEONCLOSE  = 0x00000008 # VFS only\n  EXCLUSIVE      = 0x00000010 # VFS only\n  AUTOPROXY      = 0x00000020 # VFS only\n  URI            = 0x00000040 # Ok for sqlite3_open_v2()\n  MEMORY         = 0x00000080 # Ok for sqlite3_open_v2()\n  MAIN_DB        = 0x00000100 # VFS only\n  TEMP_DB        = 0x00000200 # VFS only\n  TRANSIENT_DB   = 0x00000400 # VFS only\n  MAIN_JOURNAL   = 0x00000800 # VFS only\n  TEMP_JOURNAL   = 0x00001000 # VFS only\n  SUBJOURNAL     = 0x00002000 # VFS only\n  MASTER_JOURNAL = 0x00004000 # VFS only\n  NOMUTEX        = 0x00008000 # Ok for sqlite3_open_v2()\n  FULLMUTEX      = 0x00010000 # Ok for sqlite3_open_v2()\n  SHAREDCACHE    = 0x00020000 # Ok for sqlite3_open_v2()\n  PRIVATECACHE   = 0x00040000 # Ok for sqlite3_open_v2()\n  WAL            = 0x00080000 # VFS only\nend\n\nmodule SQLite3\n  # Same as doing SQLite3::Flag.flag(*values)\n  macro flags(*values)\n    ::SQLite3::Flag.flags({{*values}})\n  end\nend\n"
  },
  {
    "path": "src/sqlite3/lib_sqlite3.cr",
    "content": "require \"./type\"\n\n@[Link(\"sqlite3\")]\n{% if flag?(:msvc) %}\n  @[Link(dll: \"sqlite3.dll\")]\n{% end %}\nlib LibSQLite3\n  type SQLite3 = Void*\n  type Statement = Void*\n  type SQLite3Backup = Void*\n  type SQLite3Context = Void*\n  type SQLite3Value = Void*\n\n  enum Code\n    # Successful result\n    OKAY = 0\n    # Generic error\n    ERROR = 1\n    # Internal logic error in SQLite\n    INTERNAL = 2\n    # Access permission denied\n    PERM = 3\n    # Callback routine requested an abort\n    ABORT = 4\n    # The database file is locked\n    BUSY = 5\n    # A table in the database is locked\n    LOCKED = 6\n    # A malloc() failed\n    NOMEM = 7\n    # Attempt to write a readonly database\n    READONLY = 8\n    # Operation terminated by sqlite3_interrupt()\n    INTERRUPT = 9\n    # Some kind of disk I/O error occurred\n    IOERR = 10\n    # The database disk image is malformed\n    CORRUPT = 11\n    # Unknown opcode in sqlite3_file_control()\n    NOTFOUND = 12\n    # Insertion failed because database is full\n    FULL = 13\n    # Unable to open the database file\n    CANTOPEN = 14\n    # Database lock protocol error\n    PROTOCOL = 15\n    # Internal use only\n    EMPTY = 16\n    # The database schema changed\n    SCHEMA = 17\n    # String or BLOB exceeds size limit\n    TOOBIG = 18\n    # Abort due to constraint violation\n    CONSTRAINT = 19\n    # Data type mismatch\n    MISMATCH = 20\n    # Library used incorrectly\n    MISUSE = 21\n    # Uses OS features not supported on host\n    NOLFS = 22\n    # Authorization denied\n    AUTH = 23\n    # Not used\n    FORMAT = 24\n    # 2nd parameter to sqlite3_bind out of range\n    RANGE = 25\n    # File opened that is not a database file\n    NOTADB = 26\n    # Notifications from sqlite3_log()\n    NOTICE = 27\n    # Warnings from sqlite3_log()\n    WARNING = 28\n    # sqlite3_step() has another row ready\n    ROW = 100\n    # sqlite3_step() has finished executing\n    DONE = 101\n  end\n\n  alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32\n  alias FuncCallback = (SQLite3Context, Int32, SQLite3Value*) -> Void\n\n  fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags : ::SQLite3::Flag, zVfs : UInt8*) : Int32\n\n  fun errcode = sqlite3_errcode(SQLite3) : Int32\n  fun errmsg = sqlite3_errmsg(SQLite3) : UInt8*\n\n  fun backup_init = sqlite3_backup_init(SQLite3, UInt8*, SQLite3, UInt8*) : SQLite3Backup\n  fun backup_step = sqlite3_backup_step(SQLite3Backup, Int32) : Code\n  fun backup_finish = sqlite3_backup_finish(SQLite3Backup) : Code\n\n  fun prepare_v2 = sqlite3_prepare_v2(db : SQLite3, zSql : UInt8*, nByte : Int32, ppStmt : Statement*, pzTail : UInt8**) : Int32\n  fun exec = sqlite3_exec(db : SQLite3, zSql : UInt8*, pCallback : Callback, pCallbackArgs : Void*, pzErrMsg : UInt8**) : Int32\n  fun step = sqlite3_step(stmt : Statement) : Int32\n  fun column_count = sqlite3_column_count(stmt : Statement) : Int32\n  fun column_type = sqlite3_column_type(stmt : Statement, iCol : Int32) : ::SQLite3::Type\n  fun column_int64 = sqlite3_column_int64(stmt : Statement, iCol : Int32) : Int64\n  fun column_double = sqlite3_column_double(stmt : Statement, iCol : Int32) : Float64\n  fun column_text = sqlite3_column_text(stmt : Statement, iCol : Int32) : UInt8*\n  fun column_bytes = sqlite3_column_bytes(stmt : Statement, iCol : Int32) : Int32\n  fun column_blob = sqlite3_column_blob(stmt : Statement, iCol : Int32) : UInt8*\n\n  fun bind_int = sqlite3_bind_int(stmt : Statement, idx : Int32, value : Int32) : Int32\n  fun bind_int64 = sqlite3_bind_int64(stmt : Statement, idx : Int32, value : Int64) : Int32\n  fun bind_text = sqlite3_bind_text(stmt : Statement, idx : Int32, value : UInt8*, bytes : Int32, destructor : Void* ->) : Int32\n  fun bind_blob = sqlite3_bind_blob(stmt : Statement, idx : Int32, value : UInt8*, bytes : Int32, destructor : Void* ->) : Int32\n  fun bind_null = sqlite3_bind_null(stmt : Statement, idx : Int32) : Int32\n  fun bind_double = sqlite3_bind_double(stmt : Statement, idx : Int32, value : Float64) : Int32\n\n  fun bind_parameter_index = sqlite3_bind_parameter_index(stmt : Statement, name : UInt8*) : Int32\n  fun reset = sqlite3_reset(stmt : Statement) : Int32\n  fun column_name = sqlite3_column_name(stmt : Statement, idx : Int32) : UInt8*\n  fun last_insert_rowid = sqlite3_last_insert_rowid(db : SQLite3) : Int64\n  fun changes = sqlite3_changes(db : SQLite3) : Int32\n\n  fun finalize = sqlite3_finalize(stmt : Statement) : Int32\n  fun close_v2 = sqlite3_close_v2(SQLite3) : Int32\n  fun close = sqlite3_close(SQLite3) : Int32\n\n  fun create_function = sqlite3_create_function(SQLite3, funcName : UInt8*, nArg : Int32, eTextRep : Int32, pApp : Void*, xFunc : FuncCallback, xStep : Void*, xFinal : Void*) : Int32\n  fun value_text = sqlite3_value_text(SQLite3Value) : UInt8*\n  fun result_int = sqlite3_result_int(SQLite3Context, Int32) : Nil\nend\n"
  },
  {
    "path": "src/sqlite3/result_set.cr",
    "content": "class SQLite3::ResultSet < DB::ResultSet\n  @column_index = 0\n\n  protected def do_close\n    LibSQLite3.reset(self)\n    super\n  end\n\n  # Advances to the next row. Returns `true` if there's a next row,\n  # `false` otherwise. Must be called at least once to advance to the first\n  # row.\n  def move_next : Bool\n    @column_index = 0\n\n    case step\n    when LibSQLite3::Code::ROW\n      true\n    when LibSQLite3::Code::DONE\n      false\n    else\n      raise Exception.new(sqlite3_statement.sqlite3_connection)\n    end\n  end\n\n  def read\n    col = @column_index\n    value =\n      case LibSQLite3.column_type(self, col)\n      when Type::INTEGER\n        LibSQLite3.column_int64(self, col)\n      when Type::FLOAT\n        LibSQLite3.column_double(self, col)\n      when Type::BLOB\n        blob = LibSQLite3.column_blob(self, col)\n        bytes = LibSQLite3.column_bytes(self, col)\n        ptr = Pointer(UInt8).malloc(bytes)\n        ptr.copy_from(blob, bytes)\n        Bytes.new(ptr, bytes)\n      when Type::TEXT\n        String.new(LibSQLite3.column_text(self, col))\n      when Type::NULL\n        nil\n      else\n        raise Exception.new(sqlite3_statement.sqlite3_connection)\n      end\n    @column_index += 1\n    value\n  end\n\n  def next_column_index : Int32\n    @column_index\n  end\n\n  def read(t : UInt8.class) : UInt8\n    read(Int64).to_u8\n  end\n\n  def read(type : UInt8?.class) : UInt8?\n    read(Int64?).try &.to_u8\n  end\n\n  def read(t : UInt16.class) : UInt16\n    read(Int64).to_u16\n  end\n\n  def read(type : UInt16?.class) : UInt16?\n    read(Int64?).try &.to_u16\n  end\n\n  def read(t : UInt32.class) : UInt32\n    read(Int64).to_u32\n  end\n\n  def read(type : UInt32?.class) : UInt32?\n    read(Int64?).try &.to_u32\n  end\n\n  def read(t : Int8.class) : Int8\n    read(Int64).to_i8\n  end\n\n  def read(type : Int8?.class) : Int8?\n    read(Int64?).try &.to_i8\n  end\n\n  def read(t : Int16.class) : Int16\n    read(Int64).to_i16\n  end\n\n  def read(type : Int16?.class) : Int16?\n    read(Int64?).try &.to_i16\n  end\n\n  def read(t : Int32.class) : Int32\n    read(Int64).to_i32\n  end\n\n  def read(type : Int32?.class) : Int32?\n    read(Int64?).try &.to_i32\n  end\n\n  def read(t : Float32.class) : Float32\n    read(Float64).to_f32\n  end\n\n  def read(type : Float32?.class) : Float32?\n    read(Float64?).try &.to_f32\n  end\n\n  def read(t : Time.class) : Time\n    text = read(String)\n    if text.includes? \".\"\n      Time.parse text, SQLite3::DATE_FORMAT_SUBSECOND, location: SQLite3::TIME_ZONE\n    else\n      Time.parse text, SQLite3::DATE_FORMAT_SECOND, location: SQLite3::TIME_ZONE\n    end\n  end\n\n  def read(t : Time?.class) : Time?\n    read(String?).try { |v|\n      if v.includes? \".\"\n        Time.parse v, SQLite3::DATE_FORMAT_SUBSECOND, location: SQLite3::TIME_ZONE\n      else\n        Time.parse v, SQLite3::DATE_FORMAT_SECOND, location: SQLite3::TIME_ZONE\n      end\n    }\n  end\n\n  def read(t : Bool.class) : Bool\n    read(Int64) != 0\n  end\n\n  def read(t : Bool?.class) : Bool?\n    read(Int64?).try &.!=(0)\n  end\n\n  def column_count : Int32\n    LibSQLite3.column_count(self)\n  end\n\n  def column_name(index) : String\n    String.new LibSQLite3.column_name(self, index)\n  end\n\n  def to_unsafe\n    sqlite3_statement.to_unsafe\n  end\n\n  # :nodoc:\n  private def step\n    LibSQLite3::Code.new LibSQLite3.step(sqlite3_statement)\n  end\n\n  protected def sqlite3_statement\n    @statement.as(Statement)\n  end\n\n  private def moving_column(&)\n    res = yield @column_index\n    @column_index += 1\n    res\n  end\nend\n"
  },
  {
    "path": "src/sqlite3/statement.cr",
    "content": "class SQLite3::Statement < DB::Statement\n  # Keep references to bound strings and bytes to prevent them from being\n  # garbage collected before SQLite executes the statement. It is lazy\n  # initialized to help with memory footprint.\n  # See https://github.com/crystal-lang/crystal-sqlite3/pull/111 for more\n  # context.\n  @arg_refs : Array(Pointer(UInt8)) | Nil\n\n  private def clear_arg_refs\n    @arg_refs.try(&.clear)\n  end\n\n  private def track_arg_ref(ref)\n    arg_refs = @arg_refs ||= [] of Pointer(UInt8)\n    arg_refs.push(ref.to_unsafe)\n  end\n\n  def initialize(connection, command)\n    super(connection, command)\n    check LibSQLite3.prepare_v2(sqlite3_connection, command, command.bytesize + 1, out @stmt, nil)\n  end\n\n  protected def perform_query(args : Enumerable) : DB::ResultSet\n    clear_arg_refs\n    LibSQLite3.reset(self)\n    args.each_with_index(1) do |arg, index|\n      bind_arg(index, arg)\n    end\n    ResultSet.new(self)\n  end\n\n  protected def perform_exec(args : Enumerable) : DB::ExecResult\n    clear_arg_refs\n    LibSQLite3.reset(self.to_unsafe)\n    args.each_with_index(1) do |arg, index|\n      bind_arg(index, arg)\n    end\n\n    # exec\n    step = uninitialized LibSQLite3::Code\n    loop do\n      step = LibSQLite3::Code.new LibSQLite3.step(self)\n      break unless step == LibSQLite3::Code::ROW\n    end\n    raise Exception.new(sqlite3_connection) unless step == LibSQLite3::Code::DONE\n\n    rows_affected = LibSQLite3.changes(sqlite3_connection).to_i64\n    last_id = LibSQLite3.last_insert_rowid(sqlite3_connection)\n\n    DB::ExecResult.new rows_affected, last_id\n  end\n\n  protected def do_close\n    super\n    clear_arg_refs\n    check LibSQLite3.finalize(self)\n  end\n\n  private def bind_arg(index, value : Nil)\n    check LibSQLite3.bind_null(self, index)\n  end\n\n  private def bind_arg(index, value : Bool)\n    check LibSQLite3.bind_int(self, index, value ? 1 : 0)\n  end\n\n  private def bind_arg(index, value : UInt8)\n    check LibSQLite3.bind_int(self, index, value.to_i)\n  end\n\n  private def bind_arg(index, value : UInt16)\n    check LibSQLite3.bind_int(self, index, value.to_i)\n  end\n\n  private def bind_arg(index, value : UInt32)\n    check LibSQLite3.bind_int64(self, index, value.to_i64)\n  end\n\n  private def bind_arg(index, value : Int8)\n    check LibSQLite3.bind_int(self, index, value.to_i)\n  end\n\n  private def bind_arg(index, value : Int16)\n    check LibSQLite3.bind_int(self, index, value.to_i)\n  end\n\n  private def bind_arg(index, value : Int32)\n    check LibSQLite3.bind_int(self, index, value)\n  end\n\n  private def bind_arg(index, value : Int64)\n    check LibSQLite3.bind_int64(self, index, value)\n  end\n\n  private def bind_arg(index, value : Float32)\n    check LibSQLite3.bind_double(self, index, value.to_f64)\n  end\n\n  private def bind_arg(index, value : Float64)\n    check LibSQLite3.bind_double(self, index, value)\n  end\n\n  private def bind_arg(index, value : String)\n    track_arg_ref(value)\n    check LibSQLite3.bind_text(self, index, value, value.bytesize, nil)\n  end\n\n  private def bind_arg(index, value : Bytes)\n    track_arg_ref(value)\n    check LibSQLite3.bind_blob(self, index, value, value.size, nil)\n  end\n\n  private def bind_arg(index, value : Time)\n    bind_arg(index, value.in(SQLite3::TIME_ZONE).to_s(SQLite3::DATE_FORMAT_SUBSECOND))\n  end\n\n  private def bind_arg(index, value)\n    raise \"#{self.class} does not support #{value.class} params\"\n  end\n\n  private def check(code)\n    raise Exception.new(sqlite3_connection) unless code == 0\n  end\n\n  protected def sqlite3_connection\n    @connection.as(Connection)\n  end\n\n  def to_unsafe\n    @stmt\n  end\nend\n"
  },
  {
    "path": "src/sqlite3/type.cr",
    "content": "# Each of the possible types of an SQLite3 column.\nenum SQLite3::Type\n  INTEGER = 1\n  FLOAT   = 2\n  BLOB    = 4\n  NULL    = 5\n  TEXT    = 3\nend\n"
  },
  {
    "path": "src/sqlite3/version.cr",
    "content": "module SQLite3\n  VERSION = \"0.23.0\"\nend\n"
  },
  {
    "path": "src/sqlite3.cr",
    "content": "require \"db\"\nrequire \"./sqlite3/**\"\n\nmodule SQLite3\n  DATE_FORMAT_SUBSECOND = \"%F %H:%M:%S.%L\"\n  DATE_FORMAT_SECOND    = \"%F %H:%M:%S\"\n\n  alias Any = DB::Any | Int16 | Int8 | UInt32 | UInt16 | UInt8\n\n  # :nodoc:\n  TIME_ZONE = Time::Location::UTC\n\n  # :nodoc:\n  REGEXP_FN = ->(context : LibSQLite3::SQLite3Context, argc : Int32, argv : LibSQLite3::SQLite3Value*) do\n    argv = Slice.new(argv, sizeof(Void*))\n    pattern = LibSQLite3.value_text(argv[0])\n    text = LibSQLite3.value_text(argv[1])\n    LibSQLite3.result_int(context, Regex.new(String.new(pattern)).matches?(String.new(text)).to_unsafe)\n    nil\n  end\nend\n"
  }
]